Bluesky app fork with some witchin' additions 馃挮
at main 202 lines 6.8 kB view raw
1import {useState} from 'react' 2import {View} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6 7import {cleanError} from '#/lib/strings/errors' 8import {useAgent, useSession} from '#/state/session' 9import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 10import * as Toast from '#/view/com/util/Toast' 11import {atoms as a, useBreakpoints, useTheme} from '#/alf' 12import {Button, ButtonIcon, ButtonText} from '#/components/Button' 13import * as Dialog from '#/components/Dialog' 14import * as TextField from '#/components/forms/TextField' 15import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 16import {Loader} from '#/components/Loader' 17import {P, Text} from '#/components/Typography' 18import {IS_NATIVE} from '#/env' 19 20enum Stages { 21 Email, 22 ConfirmCode, 23} 24 25export function DisableEmail2FADialog({ 26 control, 27}: { 28 control: Dialog.DialogOuterProps['control'] 29}) { 30 const {_} = useLingui() 31 const t = useTheme() 32 const {gtMobile} = useBreakpoints() 33 const {currentAccount} = useSession() 34 const agent = useAgent() 35 36 const [stage, setStage] = useState<Stages>(Stages.Email) 37 const [confirmationCode, setConfirmationCode] = useState<string>('') 38 const [isProcessing, setIsProcessing] = useState<boolean>(false) 39 const [error, setError] = useState<string>('') 40 41 const onSendEmail = async () => { 42 setError('') 43 setIsProcessing(true) 44 try { 45 await agent.com.atproto.server.requestEmailUpdate() 46 setStage(Stages.ConfirmCode) 47 } catch (e) { 48 setError(cleanError(String(e))) 49 } finally { 50 setIsProcessing(false) 51 } 52 } 53 54 const onConfirmDisable = async () => { 55 setError('') 56 setIsProcessing(true) 57 try { 58 if (currentAccount?.email) { 59 await agent.com.atproto.server.updateEmail({ 60 email: currentAccount.email, 61 token: confirmationCode.trim(), 62 emailAuthFactor: false, 63 }) 64 await agent.resumeSession(agent.session!) 65 Toast.show(_(msg({message: 'Email 2FA disabled', context: 'toast'}))) 66 } 67 control.close() 68 } catch (e) { 69 const errMsg = String(e) 70 if (errMsg.includes('Token is invalid')) { 71 setError(_(msg`Invalid 2FA confirmation code.`)) 72 } else { 73 setError(cleanError(errMsg)) 74 } 75 } finally { 76 setIsProcessing(false) 77 } 78 } 79 80 return ( 81 <Dialog.Outer control={control}> 82 <Dialog.Handle /> 83 <Dialog.ScrollableInner 84 accessibilityDescribedBy="dialog-description" 85 accessibilityLabelledBy="dialog-title"> 86 <View style={[a.relative, a.gap_md, a.w_full]}> 87 <Text 88 nativeID="dialog-title" 89 style={[a.text_2xl, a.font_semi_bold, t.atoms.text]}> 90 <Trans>Disable Email 2FA</Trans> 91 </Text> 92 <P nativeID="dialog-description"> 93 {stage === Stages.ConfirmCode ? ( 94 <Trans> 95 An email has been sent to{' '} 96 {currentAccount?.email || '(no email)'}. It includes a 97 confirmation code which you can enter below. 98 </Trans> 99 ) : ( 100 <Trans> 101 To disable the email 2FA method, please verify your access to 102 the email address. 103 </Trans> 104 )} 105 </P> 106 107 {error ? <ErrorMessage message={error} /> : undefined} 108 109 {stage === Stages.Email ? ( 110 <View 111 style={[ 112 a.gap_sm, 113 gtMobile && [a.flex_row, a.justify_end, a.gap_md], 114 ]}> 115 <Button 116 testID="sendEmailButton" 117 variant="solid" 118 color="primary" 119 size={gtMobile ? 'small' : 'large'} 120 onPress={onSendEmail} 121 label={_(msg`Send verification email`)} 122 disabled={isProcessing}> 123 <ButtonText> 124 <Trans>Send verification email</Trans> 125 </ButtonText> 126 {isProcessing && <ButtonIcon icon={Loader} />} 127 </Button> 128 <Button 129 testID="haveCodeButton" 130 variant="ghost" 131 color="primary" 132 size={gtMobile ? 'small' : 'large'} 133 onPress={() => setStage(Stages.ConfirmCode)} 134 label={_(msg`I have a code`)} 135 disabled={isProcessing}> 136 <ButtonText> 137 <Trans>I have a code</Trans> 138 </ButtonText> 139 </Button> 140 </View> 141 ) : stage === Stages.ConfirmCode ? ( 142 <View> 143 <View style={[a.mb_md]}> 144 <TextField.LabelText> 145 <Trans>Confirmation code</Trans> 146 </TextField.LabelText> 147 <TextField.Root> 148 <TextField.Icon icon={Lock} /> 149 <Dialog.Input 150 testID="confirmationCode" 151 label={_(msg`Confirmation code`)} 152 autoCapitalize="none" 153 autoFocus 154 autoCorrect={false} 155 autoComplete="off" 156 value={confirmationCode} 157 onChangeText={setConfirmationCode} 158 onSubmitEditing={onConfirmDisable} 159 editable={!isProcessing} 160 /> 161 </TextField.Root> 162 </View> 163 <View 164 style={[ 165 a.gap_sm, 166 gtMobile && [a.flex_row, a.justify_end, a.gap_md], 167 ]}> 168 <Button 169 testID="resendCodeBtn" 170 variant="ghost" 171 color="primary" 172 size={gtMobile ? 'small' : 'large'} 173 onPress={onSendEmail} 174 label={_(msg`Resend email`)} 175 disabled={isProcessing}> 176 <ButtonText> 177 <Trans>Resend email</Trans> 178 </ButtonText> 179 </Button> 180 <Button 181 testID="confirmBtn" 182 variant="solid" 183 color="primary" 184 size={gtMobile ? 'small' : 'large'} 185 onPress={onConfirmDisable} 186 label={_(msg`Confirm`)} 187 disabled={isProcessing}> 188 <ButtonText> 189 <Trans>Confirm</Trans> 190 </ButtonText> 191 {isProcessing && <ButtonIcon icon={Loader} />} 192 </Button> 193 </View> 194 </View> 195 ) : undefined} 196 197 {!gtMobile && IS_NATIVE && <View style={{height: 40}} />} 198 </View> 199 </Dialog.ScrollableInner> 200 </Dialog.Outer> 201 ) 202}