Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at f8975036440051185486f6b2c00a201ef2e18a8c 301 lines 9.1 kB view raw
1import {useState} from 'react' 2import {useWindowDimensions, View} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import * as EmailValidator from 'email-validator' 6 7import {cleanError, isNetworkError} from '#/lib/strings/errors' 8import {checkAndFormatResetCode} from '#/lib/strings/password' 9import {logger} from '#/logger' 10import {useAgent, useSession} from '#/state/session' 11import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 12import {android, atoms as a, web} from '#/alf' 13import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14import * as Dialog from '#/components/Dialog' 15import * as TextField from '#/components/forms/TextField' 16import {Loader} from '#/components/Loader' 17import {Text} from '#/components/Typography' 18import {IS_NATIVE} from '#/env' 19 20enum Stages { 21 RequestCode = 'RequestCode', 22 ChangePassword = 'ChangePassword', 23 Done = 'Done', 24} 25 26export function ChangePasswordDialog({ 27 control, 28}: { 29 control: Dialog.DialogControlProps 30}) { 31 const {height} = useWindowDimensions() 32 33 return ( 34 <Dialog.Outer 35 control={control} 36 nativeOptions={android({minHeight: height / 2})}> 37 <Dialog.Handle /> 38 <Inner /> 39 </Dialog.Outer> 40 ) 41} 42 43function Inner() { 44 const {_} = useLingui() 45 const {currentAccount} = useSession() 46 const agent = useAgent() 47 const control = Dialog.useDialogContext() 48 49 const [stage, setStage] = useState(Stages.RequestCode) 50 const [isProcessing, setIsProcessing] = useState(false) 51 const [resetCode, setResetCode] = useState('') 52 const [newPassword, setNewPassword] = useState('') 53 const [error, setError] = useState('') 54 55 const uiStrings = { 56 RequestCode: { 57 title: _(msg`Change your password`), 58 message: _( 59 msg`If you want to change your password, we will send you a code to verify that this is your account.`, 60 ), 61 }, 62 ChangePassword: { 63 title: _(msg`Enter code`), 64 message: _( 65 msg`Please enter the code you received and the new password you would like to use.`, 66 ), 67 }, 68 Done: { 69 title: _(msg`Password changed`), 70 message: _( 71 msg`Your password has been changed successfully! Please use your new password when you sign in to Bluesky from now on.`, 72 ), 73 }, 74 } 75 76 const onRequestCode = async () => { 77 if ( 78 !currentAccount?.email || 79 !EmailValidator.validate(currentAccount.email) 80 ) { 81 return setError(_(msg`Your email appears to be invalid.`)) 82 } 83 84 setError('') 85 setIsProcessing(true) 86 try { 87 await agent.com.atproto.server.requestPasswordReset({ 88 email: currentAccount.email, 89 }) 90 setStage(Stages.ChangePassword) 91 } catch (e: any) { 92 if (isNetworkError(e)) { 93 setError( 94 _( 95 msg`Unable to contact your service. Please check your internet connection and try again.`, 96 ), 97 ) 98 } else { 99 logger.error('Failed to request password reset', {safeMessage: e}) 100 setError(cleanError(e)) 101 } 102 } finally { 103 setIsProcessing(false) 104 } 105 } 106 107 const onChangePassword = async () => { 108 const formattedCode = checkAndFormatResetCode(resetCode) 109 if (!formattedCode) { 110 setError( 111 _( 112 msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`, 113 ), 114 ) 115 return 116 } 117 if (!newPassword) { 118 setError( 119 _(msg`Please enter a password. It must be at least 8 characters long.`), 120 ) 121 return 122 } 123 if (newPassword.length < 8) { 124 setError(_(msg`Password must be at least 8 characters long.`)) 125 return 126 } 127 128 setError('') 129 setIsProcessing(true) 130 try { 131 await agent.com.atproto.server.resetPassword({ 132 token: formattedCode, 133 password: newPassword, 134 }) 135 setStage(Stages.Done) 136 } catch (e: any) { 137 if (isNetworkError(e)) { 138 setError( 139 _( 140 msg`Unable to contact your service. Please check your internet connection and try again.`, 141 ), 142 ) 143 } else if (e?.toString().includes('Token is invalid')) { 144 setError(_(msg`This confirmation code is not valid. Please try again.`)) 145 } else { 146 logger.error('Failed to set new password', {safeMessage: e}) 147 setError(cleanError(e)) 148 } 149 } finally { 150 setIsProcessing(false) 151 } 152 } 153 154 const onBlur = () => { 155 const formattedCode = checkAndFormatResetCode(resetCode) 156 if (!formattedCode) { 157 return 158 } 159 setResetCode(formattedCode) 160 } 161 162 return ( 163 <Dialog.ScrollableInner 164 label={_(msg`Change password dialog`)} 165 style={web({maxWidth: 400})}> 166 <View style={[a.gap_xl]}> 167 <View style={[a.gap_sm]}> 168 <Text style={[a.font_bold, a.text_2xl]}> 169 {uiStrings[stage].title} 170 </Text> 171 {error ? ( 172 <View style={[a.rounded_sm, a.overflow_hidden]}> 173 <ErrorMessage message={error} /> 174 </View> 175 ) : null} 176 177 <Text style={[a.text_md, a.leading_snug]}> 178 {uiStrings[stage].message} 179 </Text> 180 </View> 181 182 {stage === Stages.ChangePassword && ( 183 <View style={[a.gap_md]}> 184 <View> 185 <TextField.LabelText> 186 <Trans>Confirmation code</Trans> 187 </TextField.LabelText> 188 <TextField.Root> 189 <TextField.Input 190 label={_(msg`Confirmation code`)} 191 placeholder="XXXXX-XXXXX" 192 value={resetCode} 193 onChangeText={setResetCode} 194 onBlur={onBlur} 195 autoCapitalize="none" 196 autoCorrect={false} 197 autoComplete="one-time-code" 198 /> 199 </TextField.Root> 200 </View> 201 <View> 202 <TextField.LabelText> 203 <Trans>New password</Trans> 204 </TextField.LabelText> 205 <TextField.Root> 206 <TextField.Input 207 label={_(msg`New password`)} 208 placeholder={_(msg`At least 8 characters`)} 209 value={newPassword} 210 onChangeText={setNewPassword} 211 secureTextEntry 212 autoCapitalize="none" 213 autoComplete="new-password" 214 passwordRules="minlength: 8;" 215 /> 216 </TextField.Root> 217 </View> 218 </View> 219 )} 220 221 <View style={[a.gap_sm]}> 222 {stage === Stages.RequestCode ? ( 223 <> 224 <Button 225 label={_(msg`Request code`)} 226 color="primary" 227 size="large" 228 disabled={isProcessing} 229 onPress={onRequestCode}> 230 <ButtonText> 231 <Trans>Request code</Trans> 232 </ButtonText> 233 {isProcessing && <ButtonIcon icon={Loader} />} 234 </Button> 235 <Button 236 label={_(msg`Already have a code?`)} 237 onPress={() => setStage(Stages.ChangePassword)} 238 size="large" 239 color="primary_subtle" 240 disabled={isProcessing}> 241 <ButtonText> 242 <Trans>Already have a code?</Trans> 243 </ButtonText> 244 </Button> 245 {IS_NATIVE && ( 246 <Button 247 label={_(msg`Cancel`)} 248 color="secondary" 249 size="large" 250 disabled={isProcessing} 251 onPress={() => control.close()}> 252 <ButtonText> 253 <Trans>Cancel</Trans> 254 </ButtonText> 255 </Button> 256 )} 257 </> 258 ) : stage === Stages.ChangePassword ? ( 259 <> 260 <Button 261 label={_(msg`Change password`)} 262 color="primary" 263 size="large" 264 disabled={isProcessing} 265 onPress={onChangePassword}> 266 <ButtonText> 267 <Trans>Change password</Trans> 268 </ButtonText> 269 {isProcessing && <ButtonIcon icon={Loader} />} 270 </Button> 271 <Button 272 label={_(msg`Back`)} 273 color="secondary" 274 size="large" 275 disabled={isProcessing} 276 onPress={() => { 277 setResetCode('') 278 setStage(Stages.RequestCode) 279 }}> 280 <ButtonText> 281 <Trans>Back</Trans> 282 </ButtonText> 283 </Button> 284 </> 285 ) : stage === Stages.Done ? ( 286 <Button 287 label={_(msg`Close`)} 288 color="primary" 289 size="large" 290 onPress={() => control.close()}> 291 <ButtonText> 292 <Trans>Close</Trans> 293 </ButtonText> 294 </Button> 295 ) : null} 296 </View> 297 </View> 298 <Dialog.Close /> 299 </Dialog.ScrollableInner> 300 ) 301}