first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
import { useState, Dispatch, SetStateAction } from 'react'
import { useTranslation, Trans } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import LeaveModalForm, { LeaveModalFormProps } from './modal-form'
import OLButton from '@/features/ui/components/ol/ol-button'
import {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/ol/ol-modal'
const WRITEFULL_SUPPORT_EMAIL = 'support@writefull.com'
type LeaveModalContentProps = {
handleHide: () => void
inFlight: boolean
setInFlight: Dispatch<SetStateAction<boolean>>
}
function LeaveModalContentBlock({
setInFlight,
isFormValid,
setIsFormValid,
}: LeaveModalFormProps) {
const { t } = useTranslation()
const { isOverleaf } = getMeta('ol-ExposedSettings')
const hasPassword = getMeta('ol-hasPassword')
if (isOverleaf && !hasPassword) {
return (
<p>
<b>
<a href="/user/password/reset">{t('delete_acct_no_existing_pw')}</a>
</b>
</p>
)
}
return (
<>
<LeaveModalForm
setInFlight={setInFlight}
isFormValid={isFormValid}
setIsFormValid={setIsFormValid}
/>
<p>
<Trans
i18nKey="to_delete_your_writefull_account"
values={{ email: WRITEFULL_SUPPORT_EMAIL }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={{
// eslint-disable-next-line jsx-a11y/anchor-has-content
a: <a href={`mailto:${WRITEFULL_SUPPORT_EMAIL}`} />,
}}
/>
</p>
</>
)
}
function LeaveModalContent({
handleHide,
inFlight,
setInFlight,
}: LeaveModalContentProps) {
const { t } = useTranslation()
const [isFormValid, setIsFormValid] = useState(false)
return (
<>
<OLModalHeader closeButton>
<OLModalTitle>{t('delete_account')}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
<Trans
i18nKey="delete_account_warning_message_3"
components={{ strong: <strong /> }}
/>
</p>
<LeaveModalContentBlock
setInFlight={setInFlight}
isFormValid={isFormValid}
setIsFormValid={setIsFormValid}
/>
</OLModalBody>
<OLModalFooter>
<OLButton disabled={inFlight} onClick={handleHide} variant="secondary">
{t('cancel')}
</OLButton>
<OLButton
form="leave-form"
type="submit"
variant="danger"
disabled={inFlight || !isFormValid}
>
{inFlight ? <>{t('deleting')}</> : t('delete')}
</OLButton>
</OLModalFooter>
</>
)
}
export default LeaveModalContent

View File

@@ -0,0 +1,51 @@
import { useTranslation, Trans } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { FetchError } from '../../../../infrastructure/fetch-json'
import OLNotification from '@/features/ui/components/ol/ol-notification'
type LeaveModalFormErrorProps = {
error: FetchError
}
function LeaveModalFormError({ error }: LeaveModalFormErrorProps) {
const { t } = useTranslation()
const { isOverleaf } = getMeta('ol-ExposedSettings')
let errorMessage
let errorTip = null
if (error.response?.status === 403) {
errorMessage = t('email_or_password_wrong_try_again')
if (isOverleaf) {
errorTip = (
<Trans
i18nKey="user_deletion_password_reset_tip"
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
components={[<a href="/user/password/reset" />]}
/>
)
}
} else if (error.data?.error === 'SubscriptionAdminDeletionError') {
errorMessage = t('subscription_admins_cannot_be_deleted')
} else {
errorMessage = t('user_deletion_error')
}
return (
<OLNotification
type="error"
content={
<>
{errorMessage}
{errorTip ? (
<>
<br />
{errorTip}
</>
) : null}
</>
}
/>
)
}
export default LeaveModalFormError

View File

@@ -0,0 +1,118 @@
import { useState, useEffect, Dispatch, SetStateAction } from 'react'
import { useTranslation, Trans } from 'react-i18next'
import { postJSON, FetchError } from '../../../../infrastructure/fetch-json'
import getMeta from '../../../../utils/meta'
import LeaveModalFormError from './modal-form-error'
import { useLocation } from '../../../../shared/hooks/use-location'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
export type LeaveModalFormProps = {
setInFlight: Dispatch<SetStateAction<boolean>>
isFormValid: boolean
setIsFormValid: Dispatch<SetStateAction<boolean>>
}
function LeaveModalForm({
setInFlight,
isFormValid,
setIsFormValid,
}: LeaveModalFormProps) {
const { t } = useTranslation()
const userDefaultEmail = getMeta('ol-usersEmail')!
const location = useLocation()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmation, setConfirmation] = useState(false)
const [error, setError] = useState<FetchError | null>(null)
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value)
}
const handlePasswordChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPassword(event.target.value)
}
const handleConfirmationChange = () => {
setConfirmation(prev => !prev)
}
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
if (!isFormValid) {
return
}
setError(null)
setInFlight(true)
postJSON('/user/delete', {
body: {
password,
},
})
.then(() => {
location.assign('/')
})
.catch(setError)
.finally(() => {
setInFlight(false)
})
}
useEffect(() => {
setIsFormValid(
!!email &&
email.toLowerCase() === userDefaultEmail.toLowerCase() &&
password.length > 0 &&
confirmation
)
}, [setIsFormValid, userDefaultEmail, email, password, confirmation])
return (
<form id="leave-form" onSubmit={handleSubmit}>
<OLFormGroup controlId="email-input">
<OLFormLabel>{t('email')}</OLFormLabel>
<OLFormControl
type="text"
placeholder={t('email')}
required
value={email}
onChange={handleEmailChange}
/>
</OLFormGroup>
<OLFormGroup controlId="password-input">
<OLFormLabel>{t('password')}</OLFormLabel>
<OLFormControl
type="password"
placeholder={t('password')}
required
value={password}
onChange={handlePasswordChange}
/>
</OLFormGroup>
<OLFormCheckbox
id="confirm-account-deletion"
required
checked={confirmation}
onChange={handleConfirmationChange}
label={
<Trans
i18nKey="delete_account_confirmation_label"
components={[<i />]} // eslint-disable-line react/jsx-key
values={{
userDefaultEmail,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
}
/>
{error ? <LeaveModalFormError error={error} /> : null}
</form>
)
}
export default LeaveModalForm

View File

@@ -0,0 +1,30 @@
import { useState, useCallback } from 'react'
import LeaveModalContent from './modal-content'
import OLModal from '@/features/ui/components/ol/ol-modal'
type LeaveModalProps = {
isOpen: boolean
handleClose: () => void
}
function LeaveModal({ isOpen, handleClose }: LeaveModalProps) {
const [inFlight, setInFlight] = useState(false)
const handleHide = useCallback(() => {
if (!inFlight) {
handleClose()
}
}, [handleClose, inFlight])
return (
<OLModal animation show={isOpen} onHide={handleHide} id="leave-modal">
<LeaveModalContent
handleHide={handleHide}
inFlight={inFlight}
setInFlight={setInFlight}
/>
</OLModal>
)
}
export default LeaveModal