Bluesky app fork with some witchin' additions 馃挮
at readme-update 398 lines 12 kB view raw
1import {useReducer} 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 {ResendEmailText} from '#/components/dialogs/EmailDialog/components/ResendEmailText' 14import { 15 isValidCode, 16 TokenField, 17} from '#/components/dialogs/EmailDialog/components/TokenField' 18import {useConfirmEmail} from '#/components/dialogs/EmailDialog/data/useConfirmEmail' 19import {useRequestEmailVerification} from '#/components/dialogs/EmailDialog/data/useRequestEmailVerification' 20import {useOnEmailVerified} from '#/components/dialogs/EmailDialog/events' 21import { 22 ScreenID, 23 type ScreenProps, 24} from '#/components/dialogs/EmailDialog/types' 25import {Divider} from '#/components/Divider' 26import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 27import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' 28import {createStaticClick, InlineLinkText} from '#/components/Link' 29import {Loader} from '#/components/Loader' 30import {Span, Text} from '#/components/Typography' 31 32type State = { 33 step: 'email' | 'token' | 'success' 34 mutationStatus: 'pending' | 'success' | 'error' | 'default' 35 error: string 36 token: string 37} 38 39type Action = 40 | { 41 type: 'setStep' 42 step: State['step'] 43 } 44 | { 45 type: 'setError' 46 error: string 47 } 48 | { 49 type: 'setMutationStatus' 50 status: State['mutationStatus'] 51 } 52 | { 53 type: 'setToken' 54 value: string 55 } 56 57function reducer(state: State, action: Action): State { 58 switch (action.type) { 59 case 'setStep': { 60 return { 61 ...state, 62 error: '', 63 mutationStatus: 'default', 64 step: action.step, 65 } 66 } 67 case 'setError': { 68 return { 69 ...state, 70 error: action.error, 71 mutationStatus: 'error', 72 } 73 } 74 case 'setMutationStatus': { 75 return { 76 ...state, 77 error: '', 78 mutationStatus: action.status, 79 } 80 } 81 case 'setToken': { 82 return { 83 ...state, 84 error: '', 85 token: action.value, 86 } 87 } 88 } 89} 90 91export function Verify({config, showScreen}: ScreenProps<ScreenID.Verify>) { 92 const t = useTheme() 93 const {_} = useLingui() 94 const cleanError = useCleanError() 95 const {currentAccount} = useSession() 96 const [state, dispatch] = useReducer(reducer, { 97 step: 'email', 98 mutationStatus: 'default', 99 error: '', 100 token: '', 101 }) 102 103 const {mutateAsync: requestEmailVerification} = useRequestEmailVerification() 104 const {mutateAsync: confirmEmail} = useConfirmEmail() 105 106 useOnEmailVerified(() => { 107 if (config.onVerify) { 108 config.onVerify() 109 } else { 110 dispatch({ 111 type: 'setStep', 112 step: 'success', 113 }) 114 } 115 }) 116 117 const handleRequestEmailVerification = async () => { 118 dispatch({ 119 type: 'setMutationStatus', 120 status: 'pending', 121 }) 122 123 try { 124 await wait(1000, requestEmailVerification()) 125 dispatch({ 126 type: 'setMutationStatus', 127 status: 'success', 128 }) 129 } catch (e) { 130 logger.error('EmailDialog: sending verification email failed', { 131 safeMessage: e, 132 }) 133 const {clean} = cleanError(e) 134 dispatch({ 135 type: 'setError', 136 error: clean || _(msg`Failed to send email, please try again.`), 137 }) 138 } 139 } 140 141 const handleConfirmEmail = async () => { 142 if (!isValidCode(state.token)) { 143 dispatch({ 144 type: 'setError', 145 error: _(msg`Please enter a valid code.`), 146 }) 147 return 148 } 149 150 dispatch({ 151 type: 'setMutationStatus', 152 status: 'pending', 153 }) 154 155 try { 156 await wait(1000, confirmEmail({token: state.token})) 157 dispatch({ 158 type: 'setStep', 159 step: 'success', 160 }) 161 } catch (e) { 162 logger.error('EmailDialog: confirming email failed', { 163 safeMessage: e, 164 }) 165 const {clean} = cleanError(e) 166 dispatch({ 167 type: 'setError', 168 error: clean || _(msg`Failed to verify email, please try again.`), 169 }) 170 } 171 } 172 173 if (state.step === 'success') { 174 return ( 175 <View style={[a.gap_lg]}> 176 <View style={[a.gap_sm]}> 177 <Text style={[a.text_xl, a.font_bold]}> 178 <Span style={{top: 1}}> 179 <Check size="sm" fill={t.palette.positive_500} /> 180 </Span> 181 {' '} 182 <Trans>Email verification complete!</Trans> 183 </Text> 184 185 <Text 186 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 187 <Trans> 188 You have successfully verified your email address. You can close 189 this dialog. 190 </Trans> 191 </Text> 192 </View> 193 </View> 194 ) 195 } 196 197 return ( 198 <View style={[a.gap_lg]}> 199 <View style={[a.gap_sm]}> 200 <Text style={[a.text_xl, a.font_bold]}> 201 {state.step === 'email' ? ( 202 state.mutationStatus === 'success' ? ( 203 <> 204 <Span style={{top: 1}}> 205 <Check size="sm" fill={t.palette.positive_500} /> 206 </Span> 207 {' '} 208 <Trans>Email sent!</Trans> 209 </> 210 ) : ( 211 <Trans>Verify your email</Trans> 212 ) 213 ) : ( 214 <Trans comment="Dialog title when a user is verifying their email address by entering a code they have been sent"> 215 Verify email code 216 </Trans> 217 )} 218 </Text> 219 220 {state.step === 'email' && state.mutationStatus !== 'success' && ( 221 <> 222 {config.instructions?.map((int, i) => ( 223 <Text 224 key={i} 225 style={[ 226 a.italic, 227 a.text_sm, 228 a.leading_snug, 229 t.atoms.text_contrast_medium, 230 ]}> 231 {int} 232 </Text> 233 ))} 234 </> 235 )} 236 237 <Text style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 238 {state.step === 'email' ? ( 239 state.mutationStatus === 'success' ? ( 240 <Trans> 241 We sent an email to{' '} 242 <Span style={[a.font_semi_bold, t.atoms.text]}> 243 {currentAccount!.email} 244 </Span>{' '} 245 containing a link. Please click on it to complete the email 246 verification process. 247 </Trans> 248 ) : ( 249 <Trans> 250 We'll send an email to{' '} 251 <Span style={[a.font_semi_bold, t.atoms.text]}> 252 {currentAccount!.email} 253 </Span>{' '} 254 containing a link. Please click on it to complete the email 255 verification process. 256 </Trans> 257 ) 258 ) : ( 259 <Trans> 260 Please enter the code we sent to{' '} 261 <Span style={[a.font_semi_bold, t.atoms.text]}> 262 {currentAccount!.email} 263 </Span>{' '} 264 below. 265 </Trans> 266 )} 267 </Text> 268 269 {state.step === 'email' && state.mutationStatus !== 'success' && ( 270 <Text 271 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 272 <Trans> 273 If you need to update your email,{' '} 274 <InlineLinkText 275 label={_(msg`Click here to update your email`)} 276 {...createStaticClick(() => { 277 showScreen({id: ScreenID.Update}) 278 })}> 279 click here 280 </InlineLinkText> 281 . 282 </Trans> 283 </Text> 284 )} 285 286 {state.step === 'email' && state.mutationStatus === 'success' && ( 287 <ResendEmailText onPress={requestEmailVerification} /> 288 )} 289 </View> 290 291 {state.step === 'email' && state.mutationStatus !== 'success' ? ( 292 <> 293 {state.error && <Admonition type="error">{state.error}</Admonition>} 294 <Button 295 label={_(msg`Send verification email`)} 296 size="large" 297 variant="solid" 298 color="primary" 299 onPress={handleRequestEmailVerification} 300 disabled={state.mutationStatus === 'pending'}> 301 <ButtonText> 302 <Trans>Send email</Trans> 303 </ButtonText> 304 <ButtonIcon 305 icon={state.mutationStatus === 'pending' ? Loader : Envelope} 306 /> 307 </Button> 308 </> 309 ) : null} 310 311 {state.step === 'email' && ( 312 <> 313 <Divider /> 314 315 <Text 316 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 317 <Trans> 318 Have a code?{' '} 319 <InlineLinkText 320 label={_(msg`Enter code`)} 321 {...createStaticClick(() => { 322 dispatch({ 323 type: 'setStep', 324 step: 'token', 325 }) 326 })}> 327 Click here. 328 </InlineLinkText> 329 </Trans> 330 </Text> 331 </> 332 )} 333 334 {state.step === 'token' ? ( 335 <> 336 <TokenField 337 value={state.token} 338 onChangeText={token => { 339 dispatch({ 340 type: 'setToken', 341 value: token, 342 }) 343 }} 344 onSubmitEditing={handleConfirmEmail} 345 /> 346 347 {state.error && <Admonition type="error">{state.error}</Admonition>} 348 349 <Button 350 label={_( 351 msg({ 352 message: `Verify code`, 353 context: `action`, 354 comment: `Button text and accessibility label for action to verify the user's email address using the code entered`, 355 }), 356 )} 357 size="large" 358 variant="solid" 359 color="primary" 360 onPress={handleConfirmEmail} 361 disabled={ 362 !state.token || 363 state.token.length !== 11 || 364 state.mutationStatus === 'pending' 365 }> 366 <ButtonText> 367 <Trans 368 context="action" 369 comment="Button text and accessibility label for action to verify the user's email address using the code entered"> 370 Verify code 371 </Trans> 372 </ButtonText> 373 {state.mutationStatus === 'pending' && <ButtonIcon icon={Loader} />} 374 </Button> 375 376 <Divider /> 377 378 <Text 379 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}> 380 <Trans> 381 Don't have a code or need a new one?{' '} 382 <InlineLinkText 383 label={_(msg`Click here to restart the verification process.`)} 384 {...createStaticClick(() => { 385 dispatch({ 386 type: 'setStep', 387 step: 'email', 388 }) 389 })}> 390 Click here. 391 </InlineLinkText> 392 </Trans> 393 </Text> 394 </> 395 ) : null} 396 </View> 397 ) 398}