forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}