Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫 forked from jollywhoppers.com/witchsky.app
Something went wrong. Try again.
8.9 kB · 319 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320import {useReducer} from 'react'import {View} from 'react-native'import {msg, Trans} from '@lingui/macro'import {useLingui} from '@lingui/react'import {validate as validateEmail} from 'email-validator'
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 {ResendEmailText} from '#/components/dialogs/EmailDialog/components/ResendEmailText'import { isValidCode, TokenField,} from '#/components/dialogs/EmailDialog/components/TokenField'import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'import {useRequestEmailVerification} from '#/components/dialogs/EmailDialog/data/useRequestEmailVerification'import {useUpdateEmail} from '#/components/dialogs/EmailDialog/data/useUpdateEmail'import { type ScreenID, type ScreenProps,} from '#/components/dialogs/EmailDialog/types'import {Divider} from '#/components/Divider'import * as TextField from '#/components/forms/TextField'import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'import {Loader} from '#/components/Loader'import {Text} from '#/components/Typography'
type State = { step: 'email' | 'token' mutationStatus: 'pending' | 'success' | 'error' | 'default' error: string emailValid: boolean email: string token: string}
type Action = | { type: 'setStep' step: State['step'] } | { type: 'setError' error: string } | { type: 'setMutationStatus' status: State['mutationStatus'] } | { type: 'setEmail' value: string } | { type: 'setToken' value: string }
function reducer(state: State, action: Action): State { switch (action.type) { case 'setStep': { return { ...state, step: action.step, } } case 'setError': { return { ...state, error: action.error, mutationStatus: 'error', } } case 'setMutationStatus': { return { ...state, error: '', mutationStatus: action.status, } } case 'setEmail': { const emailValid = validateEmail(action.value) return { ...state, step: 'email', token: '', email: action.value, emailValid, } } case 'setToken': { return { ...state, error: '', token: action.value, } } }}
export function Update(_props: ScreenProps<ScreenID.Update>) { const t = useTheme() const {_} = useLingui() const cleanError = useCleanError() const {currentAccount} = useSession() const [state, dispatch] = useReducer(reducer, { step: 'email', mutationStatus: 'default', error: '', email: '', emailValid: true, token: '', })
const {mutateAsync: updateEmail} = useUpdateEmail() const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate() const {mutateAsync: requestEmailVerification} = useRequestEmailVerification()
const handleEmailChange = (email: string) => { dispatch({ type: 'setEmail', value: email, }) }
const handleUpdateEmail = async () => { if (state.step === 'token' && !isValidCode(state.token)) { dispatch({ type: 'setError', error: _(msg`Please enter a valid code.`), }) return }
dispatch({ type: 'setMutationStatus', status: 'pending', })
if (state.emailValid === false) { dispatch({ type: 'setError', error: _(msg`Please enter a valid email address.`), }) return }
if (state.email === currentAccount!.email) { dispatch({ type: 'setError', error: _(msg`This email is already associated with your account.`), }) return }
try { const {status} = await wait( 1000, updateEmail({ email: state.email, token: state.token, }), )
if (status === 'tokenRequired') { dispatch({ type: 'setStep', step: 'token', }) dispatch({ type: 'setMutationStatus', status: 'default', }) } else if (status === 'success') { dispatch({ type: 'setMutationStatus', status: 'success', })
try { // fire off a confirmation email immediately await requestEmailVerification() } catch {} } } catch (e) { logger.error('EmailDialog: update email failed', {safeMessage: e}) const {clean} = cleanError(e) dispatch({ type: 'setError', error: clean || _(msg`Failed to update email, please try again.`), }) } }
return ( <View style={[a.gap_lg]}> <Text style={[a.text_xl, a.font_bold]}> <Trans>Update your email</Trans> </Text>
{currentAccount?.emailAuthFactor && ( <Admonition type="warning"> <Trans> If you update your email address, email 2FA will be disabled. </Trans> </Admonition> )}
<View style={[a.gap_md]}> <View> <Text style={[a.pb_sm, a.leading_snug, t.atoms.text_contrast_medium]}> <Trans>Please enter your new email address.</Trans> </Text> <TextField.Root> <TextField.Icon icon={Envelope} /> <TextField.Input label={_(msg`New email address`)} placeholder={_(msg`alice@example.com`)} defaultValue={state.email} onChangeText={ state.mutationStatus === 'success' ? undefined : handleEmailChange } keyboardType="email-address" autoComplete="email" autoCapitalize="none" onSubmitEditing={handleUpdateEmail} /> </TextField.Root> </View>
{state.step === 'token' && ( <> <Divider /> <View> <Text style={[a.text_md, a.pb_sm, a.font_semi_bold]}> <Trans>Security step required</Trans> </Text> <Text style={[a.pb_sm, a.leading_snug, t.atoms.text_contrast_medium]}> <Trans> Please enter the security code we sent to your previous email address. </Trans> </Text> <TokenField value={state.token} onChangeText={ state.mutationStatus === 'success' ? undefined : token => { dispatch({ type: 'setToken', value: token, }) } } onSubmitEditing={handleUpdateEmail} /> {state.mutationStatus !== 'success' && ( <ResendEmailText onPress={requestEmailUpdate} style={[a.pt_sm]} /> )} </View> </> )}
{state.error && <Admonition type="error">{state.error}</Admonition>} </View>
{state.mutationStatus === 'success' ? ( <> <Divider /> <View style={[a.gap_sm]}> <View style={[a.flex_row, a.gap_sm, a.align_center]}> <Check fill={t.palette.positive_500} size="xs" /> <Text style={[a.text_md, a.font_bold]}> <Trans>Success!</Trans> </Text> </View> <Text style={[a.leading_snug]}> <Trans> Please click on the link in the email we just sent you to verify your new email address. This is an important step to allow you to continue enjoying all the features of Bluesky. </Trans> </Text> </View> </> ) : ( <Button label={_(msg`Update email`)} size="large" variant="solid" color="primary" onPress={handleUpdateEmail} disabled={ !state.email || (state.step === 'token' && (!state.token || state.token.length !== 11)) || state.mutationStatus === 'pending' }> <ButtonText> <Trans>Update email</Trans> </ButtonText> {state.mutationStatus === 'pending' && <ButtonIcon icon={Loader} />} </Button> )} </View> )}