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