Bluesky app fork with some witchin' additions 馃挮
at readme-update 254 lines 7.4 kB view raw
1import {useReducer, useState} from 'react' 2import {View} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {wait} from '#/lib/async/wait' 7import {useCleanError} from '#/lib/hooks/useCleanError' 8import {logger} from '#/logger' 9import {useSession} from '#/state/session' 10import {atoms as a, useTheme} from '#/alf' 11import {Admonition} from '#/components/Admonition' 12import {Button, ButtonIcon, ButtonText} from '#/components/Button' 13import {useDialogContext} from '#/components/Dialog' 14import {ResendEmailText} from '#/components/dialogs/EmailDialog/components/ResendEmailText' 15import { 16 isValidCode, 17 TokenField, 18} from '#/components/dialogs/EmailDialog/components/TokenField' 19import {useManageEmail2FA} from '#/components/dialogs/EmailDialog/data/useManageEmail2FA' 20import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate' 21import {Divider} from '#/components/Divider' 22import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 23import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' 24import {createStaticClick, InlineLinkText} from '#/components/Link' 25import {Loader} from '#/components/Loader' 26import {Span, Text} from '#/components/Typography' 27 28type State = { 29 error: string 30 step: 'email' | 'token' 31 emailStatus: 'pending' | 'success' | 'error' | 'default' 32 tokenStatus: 'pending' | 'success' | 'error' | 'default' 33} 34 35type Action = 36 | { 37 type: 'setError' 38 error: string 39 } 40 | { 41 type: 'setStep' 42 step: 'email' | 'token' 43 } 44 | { 45 type: 'setEmailStatus' 46 status: State['emailStatus'] 47 } 48 | { 49 type: 'setTokenStatus' 50 status: State['tokenStatus'] 51 } 52 53function reducer(state: State, action: Action): State { 54 switch (action.type) { 55 case 'setError': { 56 return { 57 ...state, 58 error: action.error, 59 emailStatus: 'error', 60 tokenStatus: 'error', 61 } 62 } 63 case 'setStep': { 64 return { 65 ...state, 66 error: '', 67 step: action.step, 68 } 69 } 70 case 'setEmailStatus': { 71 return { 72 ...state, 73 error: '', 74 emailStatus: action.status, 75 } 76 } 77 case 'setTokenStatus': { 78 return { 79 ...state, 80 error: '', 81 tokenStatus: action.status, 82 } 83 } 84 default: { 85 return state 86 } 87 } 88} 89 90export function Disable() { 91 const t = useTheme() 92 const {_} = useLingui() 93 const cleanError = useCleanError() 94 const {currentAccount} = useSession() 95 const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate() 96 const {mutateAsync: manageEmail2FA} = useManageEmail2FA() 97 const control = useDialogContext() 98 99 const [token, setToken] = useState('') 100 const [state, dispatch] = useReducer(reducer, { 101 error: '', 102 step: 'email', 103 emailStatus: 'default', 104 tokenStatus: 'default', 105 }) 106 107 const handleSendEmail = async () => { 108 dispatch({type: 'setEmailStatus', status: 'pending'}) 109 try { 110 await wait(1000, requestEmailUpdate()) 111 dispatch({type: 'setEmailStatus', status: 'success'}) 112 setTimeout(() => { 113 dispatch({type: 'setStep', step: 'token'}) 114 }, 1000) 115 } catch (e) { 116 logger.error('Manage2FA: email update code request failed', { 117 safeMessage: e, 118 }) 119 const {clean} = cleanError(e) 120 dispatch({ 121 type: 'setError', 122 error: clean || _(msg`Failed to send email, please try again.`), 123 }) 124 } 125 } 126 127 const handleManageEmail2FA = async () => { 128 if (!isValidCode(token)) { 129 dispatch({ 130 type: 'setError', 131 error: _(msg`Please enter a valid code.`), 132 }) 133 return 134 } 135 136 dispatch({type: 'setTokenStatus', status: 'pending'}) 137 138 try { 139 await wait(1000, manageEmail2FA({enabled: false, token})) 140 dispatch({type: 'setTokenStatus', status: 'success'}) 141 setTimeout(() => { 142 control.close() 143 }, 1000) 144 } catch (e) { 145 logger.error('Manage2FA: disable email 2FA failed', {safeMessage: e}) 146 const {clean} = cleanError(e) 147 dispatch({ 148 type: 'setError', 149 error: clean || _(msg`Failed to update email 2FA settings`), 150 }) 151 } 152 } 153 154 return ( 155 <View style={[a.gap_sm]}> 156 <Text style={[a.text_xl, a.font_bold, a.leading_snug]}> 157 <Trans>Disable email 2FA</Trans> 158 </Text> 159 160 {state.step === 'email' ? ( 161 <> 162 <Text 163 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 164 <Trans> 165 To disable your email 2FA method, please verify your access to{' '} 166 <Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span> 167 </Trans> 168 </Text> 169 170 <View style={[a.gap_lg, a.pt_sm]}> 171 {state.error && <Admonition type="error">{state.error}</Admonition>} 172 173 <Button 174 label={_(msg`Send email`)} 175 size="large" 176 variant="solid" 177 color="primary" 178 onPress={handleSendEmail} 179 disabled={state.emailStatus === 'pending'}> 180 <ButtonText> 181 <Trans>Send email</Trans> 182 </ButtonText> 183 <ButtonIcon 184 icon={ 185 state.emailStatus === 'pending' 186 ? Loader 187 : state.emailStatus === 'success' 188 ? Check 189 : Envelope 190 } 191 /> 192 </Button> 193 194 <Divider /> 195 196 <Text 197 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 198 <Trans> 199 Have a code?{' '} 200 <InlineLinkText 201 label={_(msg`Enter code`)} 202 {...createStaticClick(() => { 203 dispatch({type: 'setStep', step: 'token'}) 204 })}> 205 Click here. 206 </InlineLinkText> 207 </Trans> 208 </Text> 209 </View> 210 </> 211 ) : ( 212 <> 213 <Text 214 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 215 <Trans> 216 To disable your email 2FA method, please verify your access to{' '} 217 <Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span> 218 </Trans> 219 </Text> 220 221 <View style={[a.gap_sm, a.py_sm]}> 222 <TokenField 223 value={token} 224 onChangeText={setToken} 225 onSubmitEditing={handleManageEmail2FA} 226 /> 227 <ResendEmailText onPress={handleSendEmail} /> 228 </View> 229 230 {state.error && <Admonition type="error">{state.error}</Admonition>} 231 232 <Button 233 label={_(msg`Disable 2FA`)} 234 size="large" 235 variant="solid" 236 color="primary" 237 onPress={handleManageEmail2FA} 238 disabled={ 239 !token || token.length !== 11 || state.tokenStatus === 'pending' 240 }> 241 <ButtonText> 242 <Trans>Disable 2FA</Trans> 243 </ButtonText> 244 {state.tokenStatus === 'pending' ? ( 245 <ButtonIcon icon={Loader} /> 246 ) : state.tokenStatus === 'success' ? ( 247 <ButtonIcon icon={Check} /> 248 ) : null} 249 </Button> 250 </> 251 )} 252 </View> 253 ) 254}