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