253 lines
7.2 KiB
TypeScript
253 lines
7.2 KiB
TypeScript
import {useReducer, useState} from 'react'
|
|
import {View} from 'react-native'
|
|
import {Trans, useLingui} from '@lingui/react/macro'
|
|
|
|
import {wait} from '#/lib/async/wait'
|
|
import {useCleanError} from '#/lib/hooks/useCleanError'
|
|
import {logger} from '#/logger'
|
|
import {useSession} from '#/state/session'
|
|
import {atoms as a, useTheme} from '#/alf'
|
|
import {Admonition} from '#/components/Admonition'
|
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|
import {useDialogContext} from '#/components/Dialog'
|
|
import {ResendEmailText} from '#/components/dialogs/EmailDialog/components/ResendEmailText'
|
|
import {
|
|
isValidCode,
|
|
TokenField,
|
|
} from '#/components/dialogs/EmailDialog/components/TokenField'
|
|
import {useManageEmail2FA} from '#/components/dialogs/EmailDialog/data/useManageEmail2FA'
|
|
import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'
|
|
import {Divider} from '#/components/Divider'
|
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
|
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
|
import {createStaticClick, InlineLinkText} from '#/components/Link'
|
|
import {Loader} from '#/components/Loader'
|
|
import {Span, Text} from '#/components/Typography'
|
|
|
|
type State = {
|
|
error: string
|
|
step: 'email' | 'token'
|
|
emailStatus: 'pending' | 'success' | 'error' | 'default'
|
|
tokenStatus: 'pending' | 'success' | 'error' | 'default'
|
|
}
|
|
|
|
type Action =
|
|
| {
|
|
type: 'setError'
|
|
error: string
|
|
}
|
|
| {
|
|
type: 'setStep'
|
|
step: 'email' | 'token'
|
|
}
|
|
| {
|
|
type: 'setEmailStatus'
|
|
status: State['emailStatus']
|
|
}
|
|
| {
|
|
type: 'setTokenStatus'
|
|
status: State['tokenStatus']
|
|
}
|
|
|
|
function reducer(state: State, action: Action): State {
|
|
switch (action.type) {
|
|
case 'setError': {
|
|
return {
|
|
...state,
|
|
error: action.error,
|
|
emailStatus: 'error',
|
|
tokenStatus: 'error',
|
|
}
|
|
}
|
|
case 'setStep': {
|
|
return {
|
|
...state,
|
|
error: '',
|
|
step: action.step,
|
|
}
|
|
}
|
|
case 'setEmailStatus': {
|
|
return {
|
|
...state,
|
|
error: '',
|
|
emailStatus: action.status,
|
|
}
|
|
}
|
|
case 'setTokenStatus': {
|
|
return {
|
|
...state,
|
|
error: '',
|
|
tokenStatus: action.status,
|
|
}
|
|
}
|
|
default: {
|
|
return state
|
|
}
|
|
}
|
|
}
|
|
|
|
export function Disable() {
|
|
const t = useTheme()
|
|
const {t: l} = useLingui()
|
|
const cleanError = useCleanError()
|
|
const {currentAccount} = useSession()
|
|
const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate()
|
|
const {mutateAsync: manageEmail2FA} = useManageEmail2FA()
|
|
const control = useDialogContext()
|
|
|
|
const [token, setToken] = useState('')
|
|
const [state, dispatch] = useReducer(reducer, {
|
|
error: '',
|
|
step: 'email',
|
|
emailStatus: 'default',
|
|
tokenStatus: 'default',
|
|
})
|
|
|
|
const handleSendEmail = async () => {
|
|
dispatch({type: 'setEmailStatus', status: 'pending'})
|
|
try {
|
|
await wait(1000, requestEmailUpdate())
|
|
dispatch({type: 'setEmailStatus', status: 'success'})
|
|
setTimeout(() => {
|
|
dispatch({type: 'setStep', step: 'token'})
|
|
}, 1000)
|
|
} catch (e) {
|
|
logger.error('Manage2FA: email update code request failed', {
|
|
safeMessage: e,
|
|
})
|
|
const {clean} = cleanError(e)
|
|
dispatch({
|
|
type: 'setError',
|
|
error: clean || l`Failed to send email, please try again.`,
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleManageEmail2FA = async () => {
|
|
if (!isValidCode(token)) {
|
|
dispatch({
|
|
type: 'setError',
|
|
error: l`Please enter a valid code.`,
|
|
})
|
|
return
|
|
}
|
|
|
|
dispatch({type: 'setTokenStatus', status: 'pending'})
|
|
|
|
try {
|
|
await wait(1000, manageEmail2FA({enabled: false, token}))
|
|
dispatch({type: 'setTokenStatus', status: 'success'})
|
|
setTimeout(() => {
|
|
control.close()
|
|
}, 1000)
|
|
} catch (e) {
|
|
logger.error('Manage2FA: disable email 2FA failed', {safeMessage: e})
|
|
const {clean} = cleanError(e)
|
|
dispatch({
|
|
type: 'setError',
|
|
error: clean || l`Failed to update email 2FA settings`,
|
|
})
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View style={[a.gap_sm]}>
|
|
<Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
|
|
<Trans>Disable email 2FA</Trans>
|
|
</Text>
|
|
{state.step === 'email' ? (
|
|
<>
|
|
<Text
|
|
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
|
<Trans>
|
|
To disable your email 2FA method, please verify your access to{' '}
|
|
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
|
|
</Trans>
|
|
</Text>
|
|
|
|
<View style={[a.gap_lg, a.pt_sm]}>
|
|
{state.error && <Admonition type="error">{state.error}</Admonition>}
|
|
|
|
<Button
|
|
label={l`Send email`}
|
|
size="large"
|
|
variant="solid"
|
|
color="primary"
|
|
onPress={handleSendEmail}
|
|
disabled={state.emailStatus === 'pending'}>
|
|
<ButtonText>
|
|
<Trans>Send email</Trans>
|
|
</ButtonText>
|
|
<ButtonIcon
|
|
icon={
|
|
state.emailStatus === 'pending'
|
|
? Loader
|
|
: state.emailStatus === 'success'
|
|
? Check
|
|
: Envelope
|
|
}
|
|
/>
|
|
</Button>
|
|
|
|
<Divider />
|
|
|
|
<Text
|
|
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
|
<Trans>
|
|
Have a code?{' '}
|
|
<InlineLinkText
|
|
label={l`Enter code`}
|
|
{...createStaticClick(() => {
|
|
dispatch({type: 'setStep', step: 'token'})
|
|
})}>
|
|
Click here.
|
|
</InlineLinkText>
|
|
</Trans>
|
|
</Text>
|
|
</View>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Text
|
|
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
|
|
<Trans>
|
|
To disable your email 2FA method, please verify your access to{' '}
|
|
<Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
|
|
</Trans>
|
|
</Text>
|
|
|
|
<View style={[a.gap_sm, a.py_sm]}>
|
|
<TokenField
|
|
value={token}
|
|
onChangeText={setToken}
|
|
onSubmitEditing={handleManageEmail2FA}
|
|
/>
|
|
<ResendEmailText onPress={handleSendEmail} />
|
|
</View>
|
|
|
|
{state.error && <Admonition type="error">{state.error}</Admonition>}
|
|
|
|
<Button
|
|
label={l`Disable 2FA`}
|
|
size="large"
|
|
variant="solid"
|
|
color="primary"
|
|
onPress={handleManageEmail2FA}
|
|
disabled={
|
|
!token || token.length !== 11 || state.tokenStatus === 'pending'
|
|
}>
|
|
<ButtonText>
|
|
<Trans>Disable 2FA</Trans>
|
|
</ButtonText>
|
|
{state.tokenStatus === 'pending' ? (
|
|
<ButtonIcon icon={Loader} />
|
|
) : state.tokenStatus === 'success' ? (
|
|
<ButtonIcon icon={Check} />
|
|
) : null}
|
|
</Button>
|
|
</>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|