Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 199 lines 5.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, isNetworkError} from '#/lib/strings/errors' 8import {checkAndFormatResetCode} from '#/lib/strings/password' 9import {logger} from '#/logger' 10import {Agent} from '#/state/session/agent' 11import {atoms as a, web} from '#/alf' 12import {Button, ButtonIcon, ButtonText} from '#/components/Button' 13import {FormError} from '#/components/forms/FormError' 14import * as TextField from '#/components/forms/TextField' 15import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 16import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' 17import {Loader} from '#/components/Loader' 18import {Text} from '#/components/Typography' 19import {useAnalytics} from '#/analytics' 20import {IS_WEB} from '#/env' 21import {FormContainer} from './FormContainer' 22 23export const SetNewPasswordForm = ({ 24 error, 25 serviceUrl, 26 setError, 27 onPressBack, 28 onPasswordSet, 29}: { 30 error: string 31 serviceUrl: string 32 setError: (v: string) => void 33 onPressBack: () => void 34 onPasswordSet: () => void 35}) => { 36 const {_} = useLingui() 37 const ax = useAnalytics() 38 39 const [isProcessing, setIsProcessing] = useState<boolean>(false) 40 const [resetCode, setResetCode] = useState<string>('') 41 const [password, setPassword] = useState<string>('') 42 43 const onPressNext = async () => { 44 // Check that the code is correct. We do this again just incase the user enters the code after their pw and we 45 // don't get to call onBlur first 46 const formattedCode = checkAndFormatResetCode(resetCode) 47 48 if (!formattedCode) { 49 setError( 50 _( 51 msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`, 52 ), 53 ) 54 ax.metric('signin:passwordResetFailure', {}) 55 return 56 } 57 58 // TODO Better password strength check 59 if (!password) { 60 setError(_(msg`Please enter a password.`)) 61 return 62 } 63 64 setError('') 65 setIsProcessing(true) 66 67 try { 68 const agent = new Agent(null, {service: serviceUrl}) 69 await agent.com.atproto.server.resetPassword({ 70 token: formattedCode, 71 password, 72 }) 73 onPasswordSet() 74 ax.metric('signin:passwordResetSuccess', {}) 75 } catch (e: any) { 76 const errMsg = e.toString() 77 logger.warn('Failed to set new password', {error: e}) 78 ax.metric('signin:passwordResetFailure', {}) 79 setIsProcessing(false) 80 if (isNetworkError(e)) { 81 setError( 82 _( 83 msg`Unable to contact your service. Please check your Internet connection.`, 84 ), 85 ) 86 } else { 87 setError(cleanError(errMsg)) 88 } 89 } 90 } 91 92 const onBlur = () => { 93 const formattedCode = checkAndFormatResetCode(resetCode) 94 if (!formattedCode) { 95 setError( 96 _( 97 msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`, 98 ), 99 ) 100 return 101 } 102 setResetCode(formattedCode) 103 } 104 105 return ( 106 <FormContainer 107 testID="setNewPasswordForm" 108 titleText={<Trans>Set new password</Trans>}> 109 <Text style={[a.leading_snug, a.mb_sm]}> 110 <Trans> 111 You will receive an email with a "reset code." Enter that code here, 112 then enter your new password. 113 </Trans> 114 </Text> 115 116 <View> 117 <TextField.LabelText> 118 <Trans>Reset code</Trans> 119 </TextField.LabelText> 120 <TextField.Root> 121 <TextField.Icon icon={Ticket} /> 122 <TextField.Input 123 testID="resetCodeInput" 124 label={_(msg`Looks like XXXXX-XXXXX`)} 125 autoCapitalize="none" 126 autoFocus={true} 127 autoCorrect={false} 128 autoComplete="off" 129 value={resetCode} 130 onChangeText={setResetCode} 131 onFocus={() => setError('')} 132 onBlur={onBlur} 133 editable={!isProcessing} 134 accessibilityHint={_( 135 msg`Input code sent to your email for password reset`, 136 )} 137 /> 138 </TextField.Root> 139 </View> 140 141 <View> 142 <TextField.LabelText> 143 <Trans>New password</Trans> 144 </TextField.LabelText> 145 <TextField.Root> 146 <TextField.Icon icon={Lock} /> 147 <TextField.Input 148 testID="newPasswordInput" 149 label={_(msg`Enter a password`)} 150 autoCapitalize="none" 151 autoCorrect={false} 152 returnKeyType="done" 153 secureTextEntry={true} 154 autoComplete="new-password" 155 passwordRules="minlength: 8;" 156 clearButtonMode="while-editing" 157 value={password} 158 onChangeText={setPassword} 159 onSubmitEditing={onPressNext} 160 editable={!isProcessing} 161 accessibilityHint={_(msg`Input new password`)} 162 /> 163 </TextField.Root> 164 </View> 165 166 <FormError error={error} /> 167 168 <View style={[web([a.flex_row, a.align_center]), a.pt_lg]}> 169 {IS_WEB && ( 170 <> 171 <Button 172 label={_(msg`Back`)} 173 variant="solid" 174 color="secondary" 175 size="large" 176 onPress={onPressBack}> 177 <ButtonText> 178 <Trans>Back</Trans> 179 </ButtonText> 180 </Button> 181 <View style={a.flex_1} /> 182 </> 183 )} 184 185 <Button 186 label={_(msg`Next`)} 187 color="primary" 188 size="large" 189 onPress={onPressNext} 190 disabled={isProcessing}> 191 <ButtonText> 192 <Trans>Next</Trans> 193 </ButtonText> 194 {isProcessing && <ButtonIcon icon={Loader} />} 195 </Button> 196 </View> 197 </FormContainer> 198 ) 199}