forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useReducer, useState} 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 {useDialogContext} from '#/components/Dialog'
14import {ResendEmailText} from '#/components/dialogs/EmailDialog/components/ResendEmailText'
15import {
16 isValidCode,
17 TokenField,
18} from '#/components/dialogs/EmailDialog/components/TokenField'
19import {useManageEmail2FA} from '#/components/dialogs/EmailDialog/data/useManageEmail2FA'
20import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'
21import {Divider} from '#/components/Divider'
22import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
23import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
24import {createStaticClick, InlineLinkText} from '#/components/Link'
25import {Loader} from '#/components/Loader'
26import {Span, Text} from '#/components/Typography'
27
28type State = {
29 error: string
30 step: 'email' | 'token'
31 emailStatus: 'pending' | 'success' | 'error' | 'default'
32 tokenStatus: 'pending' | 'success' | 'error' | 'default'
33}
34
35type Action =
36 | {
37 type: 'setError'
38 error: string
39 }
40 | {
41 type: 'setStep'
42 step: 'email' | 'token'
43 }
44 | {
45 type: 'setEmailStatus'
46 status: State['emailStatus']
47 }
48 | {
49 type: 'setTokenStatus'
50 status: State['tokenStatus']
51 }
52
53function reducer(state: State, action: Action): State {
54 switch (action.type) {
55 case 'setError': {
56 return {
57 ...state,
58 error: action.error,
59 emailStatus: 'error',
60 tokenStatus: 'error',
61 }
62 }
63 case 'setStep': {
64 return {
65 ...state,
66 error: '',
67 step: action.step,
68 }
69 }
70 case 'setEmailStatus': {
71 return {
72 ...state,
73 error: '',
74 emailStatus: action.status,
75 }
76 }
77 case 'setTokenStatus': {
78 return {
79 ...state,
80 error: '',
81 tokenStatus: action.status,
82 }
83 }
84 default: {
85 return state
86 }
87 }
88}
89
90export function Disable() {
91 const t = useTheme()
92 const {_} = useLingui()
93 const cleanError = useCleanError()
94 const {currentAccount} = useSession()
95 const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate()
96 const {mutateAsync: manageEmail2FA} = useManageEmail2FA()
97 const control = useDialogContext()
98
99 const [token, setToken] = useState('')
100 const [state, dispatch] = useReducer(reducer, {
101 error: '',
102 step: 'email',
103 emailStatus: 'default',
104 tokenStatus: 'default',
105 })
106
107 const handleSendEmail = async () => {
108 dispatch({type: 'setEmailStatus', status: 'pending'})
109 try {
110 await wait(1000, requestEmailUpdate())
111 dispatch({type: 'setEmailStatus', status: 'success'})
112 setTimeout(() => {
113 dispatch({type: 'setStep', step: 'token'})
114 }, 1000)
115 } catch (e) {
116 logger.error('Manage2FA: email update code request failed', {
117 safeMessage: e,
118 })
119 const {clean} = cleanError(e)
120 dispatch({
121 type: 'setError',
122 error: clean || _(msg`Failed to send email, please try again.`),
123 })
124 }
125 }
126
127 const handleManageEmail2FA = async () => {
128 if (!isValidCode(token)) {
129 dispatch({
130 type: 'setError',
131 error: _(msg`Please enter a valid code.`),
132 })
133 return
134 }
135
136 dispatch({type: 'setTokenStatus', status: 'pending'})
137
138 try {
139 await wait(1000, manageEmail2FA({enabled: false, token}))
140 dispatch({type: 'setTokenStatus', status: 'success'})
141 setTimeout(() => {
142 control.close()
143 }, 1000)
144 } catch (e) {
145 logger.error('Manage2FA: disable email 2FA failed', {safeMessage: e})
146 const {clean} = cleanError(e)
147 dispatch({
148 type: 'setError',
149 error: clean || _(msg`Failed to update email 2FA settings`),
150 })
151 }
152 }
153
154 return (
155 <View style={[a.gap_sm]}>
156 <Text style={[a.text_xl, a.font_bold, a.leading_snug]}>
157 <Trans>Disable email 2FA</Trans>
158 </Text>
159
160 {state.step === 'email' ? (
161 <>
162 <Text
163 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
164 <Trans>
165 To disable your email 2FA method, please verify your access to{' '}
166 <Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
167 </Trans>
168 </Text>
169
170 <View style={[a.gap_lg, a.pt_sm]}>
171 {state.error && <Admonition type="error">{state.error}</Admonition>}
172
173 <Button
174 label={_(msg`Send email`)}
175 size="large"
176 variant="solid"
177 color="primary"
178 onPress={handleSendEmail}
179 disabled={state.emailStatus === 'pending'}>
180 <ButtonText>
181 <Trans>Send email</Trans>
182 </ButtonText>
183 <ButtonIcon
184 icon={
185 state.emailStatus === 'pending'
186 ? Loader
187 : state.emailStatus === 'success'
188 ? Check
189 : Envelope
190 }
191 />
192 </Button>
193
194 <Divider />
195
196 <Text
197 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
198 <Trans>
199 Have a code?{' '}
200 <InlineLinkText
201 label={_(msg`Enter code`)}
202 {...createStaticClick(() => {
203 dispatch({type: 'setStep', step: 'token'})
204 })}>
205 Click here.
206 </InlineLinkText>
207 </Trans>
208 </Text>
209 </View>
210 </>
211 ) : (
212 <>
213 <Text
214 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
215 <Trans>
216 To disable your email 2FA method, please verify your access to{' '}
217 <Span style={[a.font_semi_bold]}>{currentAccount?.email}</Span>
218 </Trans>
219 </Text>
220
221 <View style={[a.gap_sm, a.py_sm]}>
222 <TokenField
223 value={token}
224 onChangeText={setToken}
225 onSubmitEditing={handleManageEmail2FA}
226 />
227 <ResendEmailText onPress={handleSendEmail} />
228 </View>
229
230 {state.error && <Admonition type="error">{state.error}</Admonition>}
231
232 <Button
233 label={_(msg`Disable 2FA`)}
234 size="large"
235 variant="solid"
236 color="primary"
237 onPress={handleManageEmail2FA}
238 disabled={
239 !token || token.length !== 11 || state.tokenStatus === 'pending'
240 }>
241 <ButtonText>
242 <Trans>Disable 2FA</Trans>
243 </ButtonText>
244 {state.tokenStatus === 'pending' ? (
245 <ButtonIcon icon={Loader} />
246 ) : state.tokenStatus === 'success' ? (
247 <ButtonIcon icon={Check} />
248 ) : null}
249 </Button>
250 </>
251 )}
252 </View>
253 )
254}