From c7e2e3dc294d5989889cfd82c0cc995b44c76c70 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 28 Apr 2025 13:01:05 -0500 Subject: [PATCH] Clean up state in Update step --- .../dialogs/EmailDialog/screens/Update.tsx | 192 ++++++++++++++---- 1 file changed, 148 insertions(+), 44 deletions(-) diff --git a/src/components/dialogs/EmailDialog/screens/Update.tsx b/src/components/dialogs/EmailDialog/screens/Update.tsx index 891eb58f98..d7722edb71 100644 --- a/src/components/dialogs/EmailDialog/screens/Update.tsx +++ b/src/components/dialogs/EmailDialog/screens/Update.tsx @@ -1,7 +1,8 @@ -import {useState} from 'react' +import {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 {logger} from '#/logger' @@ -22,40 +23,122 @@ import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Ch 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, + token: action.value, + } + } + } +} + export function Update(_props: {config: Exclude}) { const t = useTheme() const {_} = useLingui() const {currentAccount} = useSession() - const [email, setEmail] = useState('') - const [token, setToken] = useState('') - const [error, setError] = useState('') - const [tip, setTip] = useState('') - const [success, setSuccess] = useState(false) - const [tokenRequired, setTokenRequired] = useState(false) - const [updateStatus, setUpdateStatus] = useState<'sending' | null>(null) + 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) => { - setEmail(email) - - // reset if email is edited - if (tokenRequired) { - setToken('') - setTokenRequired(false) - } + dispatch({ + type: 'setEmail', + value: email, + }) } const handleUpdateEmail = async () => { - setError('') - setTip('') - setUpdateStatus('sending') + dispatch({ + type: 'setMutationStatus', + status: 'pending', + }) - if (email === currentAccount!.email) { - setTip(_(msg`This email is already associated with your account.`)) - setUpdateStatus(null) + 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 } @@ -63,15 +146,25 @@ export function Update(_props: {config: Exclude}) { const {status} = await wait( 1000, updateEmail({ - email, - token, + email: state.email, + token: state.token, }), ) if (status === 'tokenRequired') { - setTokenRequired(true) + dispatch({ + type: 'setStep', + step: 'token', + }) + dispatch({ + type: 'setMutationStatus', + status: 'default', + }) } else if (status === 'success') { - setSuccess(true) + dispatch({ + type: 'setMutationStatus', + status: 'success', + }) try { // fire off a confirmation email immediately @@ -80,9 +173,10 @@ export function Update(_props: {config: Exclude}) { } } catch (e) { logger.error('EmailDialog: update email failed', {safeMessage: e}) - setError(_(msg`Email updated failed, please try again.`)) - } finally { - setUpdateStatus(null) + dispatch({ + type: 'setError', + error: _(msg`Email updated failed, please try again.`), + }) } } @@ -102,8 +196,12 @@ export function Update(_props: {config: Exclude}) { }) { - {tokenRequired && ( + {state.step === 'token' && ( <> @@ -122,11 +220,20 @@ export function Update(_props: {config: Exclude}) { Check your email for a security code. { + dispatch({ + type: 'setToken', + value: token, + }) + } + } onSubmitEditing={handleUpdateEmail} /> - {!success && ( + {state.mutationStatus !== 'success' && ( }) { )} - {error && {error}} - - {tip && {tip}} + {state.error && {state.error}} - {success ? ( + {state.mutationStatus === 'success' ? ( <> @@ -164,18 +269,17 @@ export function Update(_props: {config: Exclude}) { label={_(msg`Update email`)} size="large" variant="solid" - color={success ? 'secondary' : 'primary'} + color="primary" onPress={handleUpdateEmail} disabled={ - !email || - (tokenRequired && !token) || - updateStatus === 'sending' || - success + !state.email || + (state.step === 'token' && !state.token) || + state.mutationStatus === 'pending' }> Update email - {updateStatus === 'sending' && } + {state.mutationStatus === 'pending' && } )}