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