mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {
3 ActivityIndicator,
4 SafeAreaView,
5 StyleSheet,
6 TouchableOpacity,
7 View,
8} from 'react-native'
9import {LinearGradient} from 'expo-linear-gradient'
10import {msg, Trans} from '@lingui/macro'
11import {useLingui} from '@lingui/react'
12
13import {useModalControls} from '#/state/modals'
14import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
15import {useAgent, useSession, useSessionApi} from '#/state/session'
16import {usePalette} from 'lib/hooks/usePalette'
17import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
18import {cleanError} from 'lib/strings/errors'
19import {colors, gradients, s} from 'lib/styles'
20import {useTheme} from 'lib/ThemeContext'
21import {isAndroid, isWeb} from 'platform/detection'
22import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
23import {atoms as a, useTheme as useNewTheme} from '#/alf'
24import {useDialogControl} from '#/components/Dialog'
25import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
26import {InlineLinkText} from '#/components/Link'
27import {Text as NewText} from '#/components/Typography'
28import {resetToTab} from '../../../Navigation'
29import {ErrorMessage} from '../util/error/ErrorMessage'
30import {Text} from '../util/text/Text'
31import * as Toast from '../util/Toast'
32import {ScrollView, TextInput} from './util'
33
34export const snapPoints = isAndroid ? ['90%'] : ['55%']
35
36export function Component({}: {}) {
37 const pal = usePalette('default')
38 const theme = useTheme()
39 const t = useNewTheme()
40 const {currentAccount} = useSession()
41 const agent = useAgent()
42 const {removeAccount} = useSessionApi()
43 const {_} = useLingui()
44 const {closeModal} = useModalControls()
45 const {isMobile} = useWebMediaQueries()
46 const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
47 const [confirmCode, setConfirmCode] = React.useState<string>('')
48 const [password, setPassword] = React.useState<string>('')
49 const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
50 const [error, setError] = React.useState<string>('')
51 const deactivateAccountControl = useDialogControl()
52 const onPressSendEmail = async () => {
53 setError('')
54 setIsProcessing(true)
55 try {
56 await agent.com.atproto.server.requestAccountDelete()
57 setIsEmailSent(true)
58 } catch (e: any) {
59 setError(cleanError(e))
60 }
61 setIsProcessing(false)
62 }
63 const onPressConfirmDelete = async () => {
64 if (!currentAccount?.did) {
65 throw new Error(`DeleteAccount modal: currentAccount.did is undefined`)
66 }
67
68 setError('')
69 setIsProcessing(true)
70 const token = confirmCode.replace(/\s/g, '')
71
72 try {
73 // inform chat service of intent to delete account
74 const {success} = await agent.api.chat.bsky.actor.deleteAccount(
75 undefined,
76 {
77 headers: DM_SERVICE_HEADERS,
78 },
79 )
80 if (!success) {
81 throw new Error('Failed to inform chat service of account deletion')
82 }
83 await agent.com.atproto.server.deleteAccount({
84 did: currentAccount.did,
85 password,
86 token,
87 })
88 Toast.show(_(msg`Your account has been deleted`))
89 resetToTab('HomeTab')
90 removeAccount(currentAccount)
91 closeModal()
92 } catch (e: any) {
93 setError(cleanError(e))
94 }
95 setIsProcessing(false)
96 }
97 const onCancel = () => {
98 closeModal()
99 }
100 return (
101 <SafeAreaView style={[s.flex1]}>
102 <ScrollView style={[pal.view]} keyboardShouldPersistTaps="handled">
103 <View style={[styles.titleContainer, pal.view]}>
104 <Text type="title-xl" style={[s.textCenter, pal.text]}>
105 <Trans>
106 Delete Account{' '}
107 <Text type="title-xl" style={[pal.text, s.bold]}>
108 "
109 </Text>
110 <Text
111 type="title-xl"
112 numberOfLines={1}
113 style={[
114 isMobile ? styles.titleMobile : styles.titleDesktop,
115 pal.text,
116 s.bold,
117 ]}>
118 {currentAccount?.handle}
119 </Text>
120 <Text type="title-xl" style={[pal.text, s.bold]}>
121 "
122 </Text>
123 </Trans>
124 </Text>
125 </View>
126 {!isEmailSent ? (
127 <>
128 <Text type="lg" style={[styles.description, pal.text]}>
129 <Trans>
130 For security reasons, we'll need to send a confirmation code to
131 your email address.
132 </Trans>
133 </Text>
134 {error ? (
135 <View style={s.mt10}>
136 <ErrorMessage message={error} />
137 </View>
138 ) : undefined}
139 {isProcessing ? (
140 <View style={[styles.btn, s.mt10]}>
141 <ActivityIndicator />
142 </View>
143 ) : (
144 <>
145 <TouchableOpacity
146 style={styles.mt20}
147 onPress={onPressSendEmail}
148 accessibilityRole="button"
149 accessibilityLabel={_(msg`Send email`)}
150 accessibilityHint={_(
151 msg`Sends email with confirmation code for account deletion`,
152 )}>
153 <LinearGradient
154 colors={[
155 gradients.blueLight.start,
156 gradients.blueLight.end,
157 ]}
158 start={{x: 0, y: 0}}
159 end={{x: 1, y: 1}}
160 style={[styles.btn]}>
161 <Text type="button-lg" style={[s.white, s.bold]}>
162 <Trans context="action">Send Email</Trans>
163 </Text>
164 </LinearGradient>
165 </TouchableOpacity>
166 <TouchableOpacity
167 style={[styles.btn, s.mt10]}
168 onPress={onCancel}
169 accessibilityRole="button"
170 accessibilityLabel={_(msg`Cancel account deletion`)}
171 accessibilityHint=""
172 onAccessibilityEscape={onCancel}>
173 <Text type="button-lg" style={pal.textLight}>
174 <Trans context="action">Cancel</Trans>
175 </Text>
176 </TouchableOpacity>
177 </>
178 )}
179
180 <View style={[!isWeb && a.px_xl]}>
181 <View
182 style={[
183 a.w_full,
184 a.flex_row,
185 a.gap_sm,
186 a.mt_lg,
187 a.p_lg,
188 a.rounded_sm,
189 t.atoms.bg_contrast_25,
190 ]}>
191 <CircleInfo
192 size="md"
193 style={[
194 a.relative,
195 {
196 top: -1,
197 },
198 ]}
199 />
200
201 <NewText style={[a.leading_snug, a.flex_1]}>
202 <Trans>
203 You can also temporarily deactivate your account instead,
204 and reactivate it at any time.
205 </Trans>{' '}
206 <InlineLinkText
207 label={_(
208 msg`Click here for more information on deactivating your account`,
209 )}
210 to="#"
211 onPress={e => {
212 e.preventDefault()
213 deactivateAccountControl.open()
214 return false
215 }}>
216 <Trans>Click here for more information.</Trans>
217 </InlineLinkText>
218 </NewText>
219 </View>
220 </View>
221
222 <DeactivateAccountDialog control={deactivateAccountControl} />
223 </>
224 ) : (
225 <>
226 {/* TODO: Update this label to be more concise */}
227 <Text
228 type="lg"
229 style={[pal.text, styles.description]}
230 nativeID="confirmationCode">
231 <Trans>
232 Check your inbox for an email with the confirmation code to
233 enter below:
234 </Trans>
235 </Text>
236 <TextInput
237 style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
238 placeholder={_(msg`Confirmation code`)}
239 placeholderTextColor={pal.textLight.color}
240 keyboardAppearance={theme.colorScheme}
241 value={confirmCode}
242 onChangeText={setConfirmCode}
243 accessibilityLabelledBy="confirmationCode"
244 accessibilityLabel={_(msg`Confirmation code`)}
245 accessibilityHint={_(
246 msg`Input confirmation code for account deletion`,
247 )}
248 />
249 <Text
250 type="lg"
251 style={[pal.text, styles.description]}
252 nativeID="password">
253 <Trans>Please enter your password as well:</Trans>
254 </Text>
255 <TextInput
256 style={[styles.textInput, pal.borderDark, pal.text]}
257 placeholder={_(msg`Password`)}
258 placeholderTextColor={pal.textLight.color}
259 keyboardAppearance={theme.colorScheme}
260 secureTextEntry
261 value={password}
262 onChangeText={setPassword}
263 accessibilityLabelledBy="password"
264 accessibilityLabel={_(msg`Password`)}
265 accessibilityHint={_(msg`Input password for account deletion`)}
266 />
267 {error ? (
268 <View style={styles.mt20}>
269 <ErrorMessage message={error} />
270 </View>
271 ) : undefined}
272 {isProcessing ? (
273 <View style={[styles.btn, s.mt10]}>
274 <ActivityIndicator />
275 </View>
276 ) : (
277 <>
278 <TouchableOpacity
279 style={[styles.btn, styles.evilBtn, styles.mt20]}
280 onPress={onPressConfirmDelete}
281 accessibilityRole="button"
282 accessibilityLabel={_(msg`Confirm delete account`)}
283 accessibilityHint="">
284 <Text type="button-lg" style={[s.white, s.bold]}>
285 <Trans>Delete my account</Trans>
286 </Text>
287 </TouchableOpacity>
288 <TouchableOpacity
289 style={[styles.btn, s.mt10]}
290 onPress={onCancel}
291 accessibilityRole="button"
292 accessibilityLabel={_(msg`Cancel account deletion`)}
293 accessibilityHint={_(msg`Exits account deletion process`)}
294 onAccessibilityEscape={onCancel}>
295 <Text type="button-lg" style={pal.textLight}>
296 <Trans context="action">Cancel</Trans>
297 </Text>
298 </TouchableOpacity>
299 </>
300 )}
301 </>
302 )}
303 </ScrollView>
304 </SafeAreaView>
305 )
306}
307
308const styles = StyleSheet.create({
309 titleContainer: {
310 display: 'flex',
311 flexDirection: 'row',
312 justifyContent: 'center',
313 flexWrap: 'wrap',
314 marginTop: 12,
315 marginBottom: 12,
316 marginLeft: 20,
317 marginRight: 20,
318 },
319 titleMobile: {
320 textAlign: 'center',
321 },
322 titleDesktop: {
323 textAlign: 'center',
324 overflow: 'hidden',
325 whiteSpace: 'nowrap',
326 textOverflow: 'ellipsis',
327 // @ts-ignore only rendered on web
328 maxWidth: '400px',
329 },
330 description: {
331 textAlign: 'center',
332 paddingHorizontal: 22,
333 marginBottom: 10,
334 },
335 mt20: {
336 marginTop: 20,
337 },
338 mb20: {
339 marginBottom: 20,
340 },
341 textInput: {
342 borderWidth: 1,
343 borderRadius: 6,
344 paddingHorizontal: 16,
345 paddingVertical: 12,
346 fontSize: 20,
347 marginHorizontal: 20,
348 },
349 btn: {
350 flexDirection: 'row',
351 alignItems: 'center',
352 justifyContent: 'center',
353 borderRadius: 32,
354 padding: 14,
355 marginHorizontal: 20,
356 },
357 evilBtn: {
358 backgroundColor: colors.red4,
359 },
360})