mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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_heavy]}>
178 <Span style={{top: 1}}>
179 <Check size="sm" fill={t.palette.positive_600} />
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_heavy]}>
201 {state.step === 'email' ? (
202 state.mutationStatus === 'success' ? (
203 <>
204 <Span style={{top: 1}}>
205 <Check size="sm" fill={t.palette.positive_600} />
206 </Span>
207 {' '}
208 <Trans>Email sent!</Trans>
209 </>
210 ) : (
211 <Trans>Verify your email</Trans>
212 )
213 ) : (
214 <Trans>Verify email code</Trans>
215 )}
216 </Text>
217
218 {state.step === 'email' && state.mutationStatus !== 'success' && (
219 <>
220 {config.instructions?.map((int, i) => (
221 <Text
222 key={i}
223 style={[
224 a.italic,
225 a.text_sm,
226 a.leading_snug,
227 t.atoms.text_contrast_medium,
228 ]}>
229 {int}
230 </Text>
231 ))}
232 </>
233 )}
234
235 <Text style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
236 {state.step === 'email' ? (
237 state.mutationStatus === 'success' ? (
238 <Trans>
239 We sent an email to{' '}
240 <Span style={[a.font_bold, t.atoms.text]}>
241 {currentAccount!.email}
242 </Span>{' '}
243 containing a link. Please click on it to complete the email
244 verification process.
245 </Trans>
246 ) : (
247 <Trans>
248 We'll send an email to{' '}
249 <Span style={[a.font_bold, t.atoms.text]}>
250 {currentAccount!.email}
251 </Span>{' '}
252 containing a link. Please click on it to complete the email
253 verification process.
254 </Trans>
255 )
256 ) : (
257 <Trans>
258 Please enter the code we sent to{' '}
259 <Span style={[a.font_bold, t.atoms.text]}>
260 {currentAccount!.email}
261 </Span>{' '}
262 below.
263 </Trans>
264 )}
265 </Text>
266
267 {state.step === 'email' && state.mutationStatus !== 'success' && (
268 <Text
269 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
270 <Trans>
271 If you need to update your email,{' '}
272 <InlineLinkText
273 label={_(msg`Click here to update your email`)}
274 {...createStaticClick(() => {
275 showScreen({id: ScreenID.Update})
276 })}>
277 click here
278 </InlineLinkText>
279 .
280 </Trans>
281 </Text>
282 )}
283
284 {state.step === 'email' && state.mutationStatus === 'success' && (
285 <ResendEmailText onPress={requestEmailVerification} />
286 )}
287 </View>
288
289 {state.step === 'email' && state.mutationStatus !== 'success' ? (
290 <>
291 {state.error && <Admonition type="error">{state.error}</Admonition>}
292 <Button
293 label={_(msg`Send verification email`)}
294 size="large"
295 variant="solid"
296 color="primary"
297 onPress={handleRequestEmailVerification}
298 disabled={state.mutationStatus === 'pending'}>
299 <ButtonText>
300 <Trans>Send email</Trans>
301 </ButtonText>
302 <ButtonIcon
303 icon={state.mutationStatus === 'pending' ? Loader : Envelope}
304 />
305 </Button>
306 </>
307 ) : null}
308
309 {state.step === 'email' && (
310 <>
311 <Divider />
312
313 <Text
314 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
315 <Trans>
316 Have a code?{' '}
317 <InlineLinkText
318 label={_(msg`Enter code`)}
319 {...createStaticClick(() => {
320 dispatch({
321 type: 'setStep',
322 step: 'token',
323 })
324 })}>
325 Click here.
326 </InlineLinkText>
327 </Trans>
328 </Text>
329 </>
330 )}
331
332 {state.step === 'token' ? (
333 <>
334 <TokenField
335 value={state.token}
336 onChangeText={token => {
337 dispatch({
338 type: 'setToken',
339 value: token,
340 })
341 }}
342 onSubmitEditing={handleConfirmEmail}
343 />
344
345 {state.error && <Admonition type="error">{state.error}</Admonition>}
346
347 <Button
348 label={_(msg`Verify code`)}
349 size="large"
350 variant="solid"
351 color="primary"
352 onPress={handleConfirmEmail}
353 disabled={
354 !state.token ||
355 state.token.length !== 11 ||
356 state.mutationStatus === 'pending'
357 }>
358 <ButtonText>
359 <Trans>Verify code</Trans>
360 </ButtonText>
361 {state.mutationStatus === 'pending' && <ButtonIcon icon={Loader} />}
362 </Button>
363
364 <Divider />
365
366 <Text
367 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
368 <Trans>
369 Don't have a code or need a new one?{' '}
370 <InlineLinkText
371 label={_(msg`Click here to restart the verification process.`)}
372 {...createStaticClick(() => {
373 dispatch({
374 type: 'setStep',
375 step: 'email',
376 })
377 })}>
378 Click here.
379 </InlineLinkText>
380 </Trans>
381 </Text>
382 </>
383 ) : null}
384 </View>
385 )
386}