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