Bluesky app fork with some witchin' additions 💫

Refactor delete account dialog using ALF (#9863)

authored by

DS Boyce and committed by
GitHub
49253dbd cc2255cc

+381 -359
+3
src/components/dialogs/EmailDialog/components/TokenField.tsx
··· 32 32 <TextField.Root> 33 33 <TextField.Icon icon={Shield} /> 34 34 <TextField.Input 35 + autoComplete="off" 36 + autoCorrect={false} 35 37 isInvalid={isInvalid} 36 38 label={_(msg`Confirmation code`)} 39 + maxLength={11} 37 40 placeholder="XXXXX-XXXXX" 38 41 value={value} 39 42 onChangeText={handleOnChangeText}
+7 -3
src/screens/Settings/AccountSettings.tsx
··· 3 3 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 4 4 5 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 - import {useModalControls} from '#/state/modals' 7 6 import {useSession} from '#/state/session' 8 7 import * as SettingsList from '#/screens/Settings/components/SettingsList' 9 8 import {atoms as a, useTheme} from '#/alf' ··· 27 26 import {ChangeHandleDialog} from './components/ChangeHandleDialog' 28 27 import {ChangePasswordDialog} from './components/ChangePasswordDialog' 29 28 import {DeactivateAccountDialog} from './components/DeactivateAccountDialog' 29 + import {DeleteAccountDialog} from './components/DeleteAccountDialog' 30 30 import {ExportCarDialog} from './components/ExportCarDialog' 31 31 32 32 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AccountSettings'> ··· 34 34 const t = useTheme() 35 35 const {_} = useLingui() 36 36 const {currentAccount} = useSession() 37 - const {openModal} = useModalControls() 38 37 const emailDialogControl = useEmailDialogControl() 39 38 const birthdayControl = useDialogControl() 40 39 const changeHandleControl = useDialogControl() 41 40 const changePasswordControl = useDialogControl() 42 41 const exportCarControl = useDialogControl() 43 42 const deactivateAccountControl = useDialogControl() 43 + const deleteAccountControl = useDialogControl() 44 44 45 45 return ( 46 46 <Layout.Screen> ··· 169 169 </SettingsList.PressableItem> 170 170 <SettingsList.PressableItem 171 171 label={_(msg`Delete account`)} 172 - onPress={() => openModal({name: 'delete-account'})} 172 + onPress={() => deleteAccountControl.open()} 173 173 destructive> 174 174 <SettingsList.ItemIcon icon={Trash_Stroke2_Corner2_Rounded} /> 175 175 <SettingsList.ItemText> ··· 185 185 <ChangePasswordDialog control={changePasswordControl} /> 186 186 <ExportCarDialog control={exportCarControl} /> 187 187 <DeactivateAccountDialog control={deactivateAccountControl} /> 188 + <DeleteAccountDialog 189 + control={deleteAccountControl} 190 + deactivateDialogControl={deactivateAccountControl} 191 + /> 188 192 </Layout.Screen> 189 193 ) 190 194 }
+371
src/screens/Settings/components/DeleteAccountDialog.tsx
··· 1 + import {useCallback, useRef, useState} from 'react' 2 + import {type TextInput, View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {DM_SERVICE_HEADERS} from '#/lib/constants' 7 + import {useCleanError} from '#/lib/hooks/useCleanError' 8 + import {sanitizeHandle} from '#/lib/strings/handles' 9 + import {logger} from '#/logger' 10 + import {useAgent, useSession, useSessionApi} from '#/state/session' 11 + import {atoms as a, useTheme, web} from '#/alf' 12 + import {Admonition} from '#/components/Admonition' 13 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 + import {type DialogOuterProps} from '#/components/Dialog' 15 + import { 16 + isValidCode, 17 + TokenField, 18 + } from '#/components/dialogs/EmailDialog/components/TokenField' 19 + import * as TextField from '#/components/forms/TextField' 20 + import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' 21 + import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 22 + import {createStaticClick, InlineLinkText} from '#/components/Link' 23 + import {Loader} from '#/components/Loader' 24 + import * as Prompt from '#/components/Prompt' 25 + import * as toast from '#/components/Toast' 26 + import {Span, Text} from '#/components/Typography' 27 + import {resetToTab} from '#/Navigation' 28 + 29 + const WHITESPACE_RE = /\s/gu 30 + const PASSWORD_MIN_LENGTH = 8 31 + 32 + enum Step { 33 + SEND_CODE, 34 + VERIFY_CODE, 35 + CONFIRM_DELETION, 36 + } 37 + 38 + enum EmailState { 39 + DEFAULT, 40 + PENDING, 41 + } 42 + 43 + function isPasswordValid(password: string) { 44 + return password.length >= PASSWORD_MIN_LENGTH 45 + } 46 + 47 + export function DeleteAccountDialog({ 48 + control, 49 + deactivateDialogControl, 50 + }: { 51 + control: DialogOuterProps['control'] 52 + deactivateDialogControl: DialogOuterProps['control'] 53 + }) { 54 + return ( 55 + <Prompt.Outer control={control}> 56 + <DeleteAccountDialogInner 57 + control={control} 58 + deactivateDialogControl={deactivateDialogControl} 59 + /> 60 + </Prompt.Outer> 61 + ) 62 + } 63 + 64 + function DeleteAccountDialogInner({ 65 + control, 66 + deactivateDialogControl, 67 + }: { 68 + control: DialogOuterProps['control'] 69 + deactivateDialogControl: DialogOuterProps['control'] 70 + }) { 71 + const passwordRef = useRef<TextInput | null>(null) 72 + const t = useTheme() 73 + const {_} = useLingui() 74 + const cleanError = useCleanError() 75 + const agent = useAgent() 76 + const {currentAccount} = useSession() 77 + const {removeAccount} = useSessionApi() 78 + 79 + const [emailState, setEmailState] = useState(EmailState.DEFAULT) 80 + const [emailSentCount, setEmailSentCount] = useState(0) 81 + const [step, setStep] = useState(Step.SEND_CODE) 82 + const [confirmCode, setConfirmCode] = useState('') 83 + const [password, setPassword] = useState('') 84 + const [error, setError] = useState('') 85 + 86 + const sendEmail = useCallback(async () => { 87 + if (emailState === EmailState.PENDING) { 88 + return 89 + } 90 + try { 91 + setEmailState(EmailState.PENDING) 92 + await agent.com.atproto.server.requestAccountDelete() 93 + setError('') 94 + setEmailSentCount(prevCount => prevCount + 1) 95 + setStep(Step.VERIFY_CODE) 96 + } catch (e: any) { 97 + const {clean, raw} = cleanError(e) 98 + const error = clean || raw || e 99 + setError(error) 100 + logger.error(raw || e, { 101 + message: 'Failed to send account deletion verification email', 102 + }) 103 + } finally { 104 + setEmailState(EmailState.DEFAULT) 105 + } 106 + }, [agent, cleanError, emailState, setEmailState]) 107 + 108 + const confirmDeletion = useCallback(async () => { 109 + try { 110 + setError('') 111 + if (!currentAccount?.did) { 112 + throw new Error('Invalid did') 113 + } 114 + const token = confirmCode.replace(WHITESPACE_RE, '') 115 + // Inform chat service of intent to delete account. 116 + const {success} = await agent.api.chat.bsky.actor.deleteAccount( 117 + undefined, 118 + { 119 + headers: DM_SERVICE_HEADERS, 120 + }, 121 + ) 122 + if (!success) { 123 + throw new Error('Failed to inform chat service of account deletion') 124 + } 125 + await agent.com.atproto.server.deleteAccount({ 126 + did: currentAccount.did, 127 + password, 128 + token, 129 + }) 130 + control.close(() => { 131 + toast.show(_(msg`Your account has been deleted, see ya! ✌️`)) 132 + resetToTab('HomeTab') 133 + removeAccount(currentAccount) 134 + }) 135 + } catch (e: any) { 136 + const {clean, raw} = cleanError(e) 137 + const error = clean || raw || e 138 + setError(error) 139 + logger.error(raw || e, { 140 + message: 'Failed to delete account', 141 + }) 142 + setConfirmCode('') 143 + setPassword('') 144 + setStep(Step.VERIFY_CODE) 145 + } 146 + }, [ 147 + _, 148 + agent, 149 + cleanError, 150 + confirmCode, 151 + control, 152 + currentAccount, 153 + password, 154 + removeAccount, 155 + ]) 156 + 157 + const handleDeactivate = useCallback(() => { 158 + control.close(() => deactivateDialogControl.open()) 159 + }, [control, deactivateDialogControl]) 160 + 161 + const handleSendEmail = useCallback(() => { 162 + void sendEmail() 163 + }, [sendEmail]) 164 + 165 + const handleSubmitConfirmCode = useCallback(() => { 166 + passwordRef.current?.focus() 167 + }, []) 168 + 169 + const handleDeleteAccount = useCallback(() => { 170 + setStep(Step.CONFIRM_DELETION) 171 + }, [setStep]) 172 + 173 + const handleConfirmDeletion = useCallback(() => { 174 + void confirmDeletion() 175 + }, [confirmDeletion]) 176 + 177 + const currentHandle = sanitizeHandle(currentAccount?.handle ?? '', '@') 178 + const currentEmail = currentAccount?.email ?? '(no email)' 179 + 180 + switch (step) { 181 + case Step.SEND_CODE: 182 + return ( 183 + <> 184 + <Prompt.Content> 185 + <Prompt.TitleText> 186 + {_(msg`Delete account “${currentHandle}”`)} 187 + </Prompt.TitleText> 188 + <Prompt.DescriptionText> 189 + <Trans> 190 + For security reasons, we’ll need to send a confirmation code to 191 + your email address{' '} 192 + <Span style={[a.font_semi_bold, t.atoms.text]}> 193 + {currentEmail} 194 + </Span> 195 + . 196 + </Trans> 197 + </Prompt.DescriptionText> 198 + </Prompt.Content> 199 + <Prompt.Actions> 200 + <Button 201 + color="primary" 202 + label={_(msg`Send email`)} 203 + size="large" 204 + onPress={handleSendEmail}> 205 + <ButtonText>{_(msg`Send email`)}</ButtonText> 206 + <ButtonIcon 207 + icon={emailState === EmailState.PENDING ? Loader : Envelope} 208 + /> 209 + </Button> 210 + <Prompt.Cancel /> 211 + </Prompt.Actions> 212 + {error && ( 213 + <Admonition style={[a.mt_lg]} type="error"> 214 + <Text style={[a.flex_1, a.leading_snug]}>{error}</Text> 215 + </Admonition> 216 + )} 217 + <Admonition style={[a.mt_lg]} type="tip"> 218 + <Trans> 219 + You can also{' '} 220 + <Span 221 + style={[{color: t.palette.primary_500}, web(a.underline)]} 222 + onPress={handleDeactivate}> 223 + temporarily deactivate 224 + </Span>{' '} 225 + your account instead. Your profile, posts, feeds, and lists will 226 + no longer be visible to other Bluesky users. You can reactivate 227 + your account at any time by logging in. 228 + </Trans> 229 + </Admonition> 230 + </> 231 + ) 232 + case Step.VERIFY_CODE: 233 + return ( 234 + <> 235 + <Prompt.Content> 236 + <Prompt.TitleText> 237 + {_(msg`Delete account “${currentHandle}”`)} 238 + </Prompt.TitleText> 239 + <Prompt.DescriptionText> 240 + <Trans> 241 + Check{' '} 242 + <Span style={[a.font_semi_bold, t.atoms.text]}> 243 + {currentEmail} 244 + </Span>{' '} 245 + for an email with the confirmation code to enter below: 246 + </Trans> 247 + </Prompt.DescriptionText> 248 + </Prompt.Content> 249 + <View style={[a.mb_xs]}> 250 + <TextField.LabelText> 251 + <Trans>Confirmation code</Trans> 252 + </TextField.LabelText> 253 + <TokenField 254 + value={confirmCode} 255 + onChangeText={setConfirmCode} 256 + onSubmitEditing={handleSubmitConfirmCode} 257 + /> 258 + </View> 259 + <Text 260 + style={[ 261 + a.text_sm, 262 + a.leading_snug, 263 + a.mb_lg, 264 + t.atoms.text_contrast_medium, 265 + ]}> 266 + {emailSentCount > 1 ? ( 267 + <Trans> 268 + Email sent!{' '} 269 + <InlineLinkText 270 + label={_(msg`Resend`)} 271 + {...createStaticClick(() => { 272 + void handleSendEmail() 273 + })}> 274 + Click here to resend. 275 + </InlineLinkText> 276 + </Trans> 277 + ) : ( 278 + <Trans> 279 + Don’t see a code?{' '} 280 + <InlineLinkText 281 + label={_(msg`Resend`)} 282 + {...createStaticClick(() => { 283 + void handleSendEmail() 284 + })}> 285 + Click here to resend. 286 + </InlineLinkText> 287 + </Trans> 288 + )}{' '} 289 + <Span style={{top: 1}}> 290 + {emailState === EmailState.PENDING ? <Loader size="xs" /> : null} 291 + </Span> 292 + </Text> 293 + <View style={[a.mb_xl]}> 294 + <TextField.LabelText> 295 + <Trans>Password</Trans> 296 + </TextField.LabelText> 297 + <TextField.Root> 298 + <TextField.Icon icon={Lock} /> 299 + <TextField.Input 300 + inputRef={passwordRef} 301 + testID="newPasswordInput" 302 + label={_(msg`Enter your password`)} 303 + autoCapitalize="none" 304 + autoCorrect={false} 305 + returnKeyType="done" 306 + secureTextEntry={true} 307 + autoComplete="off" 308 + clearButtonMode="while-editing" 309 + passwordRules={`minlength: ${PASSWORD_MIN_LENGTH}};`} 310 + value={password} 311 + onChangeText={setPassword} 312 + onSubmitEditing={handleDeleteAccount} 313 + /> 314 + </TextField.Root> 315 + </View> 316 + <Prompt.Actions> 317 + <Button 318 + color="negative" 319 + disabled={!isValidCode(confirmCode) || !isPasswordValid(password)} 320 + size="large" 321 + label={_(msg`Delete My Account`)} 322 + onPress={handleDeleteAccount}> 323 + <ButtonText>{_(msg`Delete My Account`)}</ButtonText> 324 + </Button> 325 + <Prompt.Cancel /> 326 + </Prompt.Actions> 327 + {error && ( 328 + <Admonition style={[a.mt_lg]} type="error"> 329 + <Text style={[a.flex_1, a.leading_snug]}>{error}</Text> 330 + </Admonition> 331 + )} 332 + </> 333 + ) 334 + case Step.CONFIRM_DELETION: 335 + return ( 336 + <> 337 + <Prompt.Content> 338 + <Prompt.TitleText> 339 + {_(msg`Are you really, really sure?`)} 340 + </Prompt.TitleText> 341 + <Prompt.DescriptionText> 342 + <Trans> 343 + This will irreversably delete your Bluesky account{' '} 344 + <Span style={[a.font_semi_bold, t.atoms.text]}> 345 + {currentHandle} 346 + </Span>{' '} 347 + and all associated data. Note that this will affect any other{' '} 348 + <InlineLinkText 349 + label={_(msg`Learn more about the AT Protocol.`)} 350 + style={[a.text_md]} 351 + to="https://bsky.social/about/faq"> 352 + AT Protocol 353 + </InlineLinkText>{' '} 354 + services you use with this account. 355 + </Trans> 356 + </Prompt.DescriptionText> 357 + </Prompt.Content> 358 + <Prompt.Actions> 359 + <Button 360 + color="negative" 361 + size="large" 362 + label={_(msg`Yes, delete my account`)} 363 + onPress={handleConfirmDeletion}> 364 + <ButtonText>{_(msg`Yes, delete my account`)}</ButtonText> 365 + </Button> 366 + <Prompt.Cancel /> 367 + </Prompt.Actions> 368 + </> 369 + ) 370 + } 371 + }
-7
src/state/modals/index.tsx
··· 11 11 onRemove?: (listUri: string) => void 12 12 } 13 13 14 - export interface DeleteAccountModal { 15 - name: 'delete-account' 16 - } 17 - 18 14 export interface ContentLanguagesSettingsModal { 19 15 name: 'content-languages-settings' 20 16 } ··· 23 19 * @deprecated DO NOT ADD NEW MODALS 24 20 */ 25 21 export type Modal = 26 - // Account 27 - | DeleteAccountModal 28 - 29 22 // Curation 30 23 | ContentLanguagesSettingsModal 31 24
-342
src/view/com/modals/DeleteAccount.tsx
··· 1 - import React from 'react' 2 - import { 3 - ActivityIndicator, 4 - SafeAreaView, 5 - StyleSheet, 6 - TouchableOpacity, 7 - View, 8 - } from 'react-native' 9 - import {LinearGradient} from 'expo-linear-gradient' 10 - import {msg, Trans} from '@lingui/macro' 11 - import {useLingui} from '@lingui/react' 12 - 13 - import {DM_SERVICE_HEADERS} from '#/lib/constants' 14 - import {usePalette} from '#/lib/hooks/usePalette' 15 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 16 - import {cleanError} from '#/lib/strings/errors' 17 - import {colors, gradients, s} from '#/lib/styles' 18 - import {useTheme} from '#/lib/ThemeContext' 19 - import {useModalControls} from '#/state/modals' 20 - import {useAgent, useSession, useSessionApi} from '#/state/session' 21 - import {atoms as a, useTheme as useNewTheme} from '#/alf' 22 - import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 23 - import {Text as NewText} from '#/components/Typography' 24 - import {IS_ANDROID, IS_WEB} from '#/env' 25 - import {resetToTab} from '../../../Navigation' 26 - import {ErrorMessage} from '../util/error/ErrorMessage' 27 - import {Text} from '../util/text/Text' 28 - import * as Toast from '../util/Toast' 29 - import {ScrollView, TextInput} from './util' 30 - 31 - export const snapPoints = IS_ANDROID ? ['90%'] : ['55%'] 32 - 33 - export 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 /> 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 - value={confirmCode} 224 - onChangeText={setConfirmCode} 225 - accessibilityLabelledBy="confirmationCode" 226 - accessibilityLabel={_(msg`Confirmation code`)} 227 - accessibilityHint={_( 228 - msg`Input confirmation code for account deletion`, 229 - )} 230 - /> 231 - <Text 232 - type="lg" 233 - style={[pal.text, styles.description]} 234 - nativeID="password"> 235 - <Trans>Please enter your password as well:</Trans> 236 - </Text> 237 - <TextInput 238 - style={[styles.textInput, pal.borderDark, pal.text]} 239 - placeholder={_(msg`Password`)} 240 - placeholderTextColor={pal.textLight.color} 241 - keyboardAppearance={theme.colorScheme} 242 - secureTextEntry 243 - value={password} 244 - onChangeText={setPassword} 245 - accessibilityLabelledBy="password" 246 - accessibilityLabel={_(msg`Password`)} 247 - accessibilityHint={_(msg`Input password for account deletion`)} 248 - /> 249 - {error ? ( 250 - <View style={styles.mt20}> 251 - <ErrorMessage message={error} /> 252 - </View> 253 - ) : undefined} 254 - {isProcessing ? ( 255 - <View style={[styles.btn, s.mt10]}> 256 - <ActivityIndicator /> 257 - </View> 258 - ) : ( 259 - <> 260 - <TouchableOpacity 261 - style={[styles.btn, styles.evilBtn, styles.mt20]} 262 - onPress={onPressConfirmDelete} 263 - accessibilityRole="button" 264 - accessibilityLabel={_(msg`Confirm delete account`)} 265 - accessibilityHint=""> 266 - <Text type="button-lg" style={[s.white, s.bold]}> 267 - <Trans>Delete my account</Trans> 268 - </Text> 269 - </TouchableOpacity> 270 - <TouchableOpacity 271 - style={[styles.btn, s.mt10]} 272 - onPress={onCancel} 273 - accessibilityRole="button" 274 - accessibilityLabel={_(msg`Cancel account deletion`)} 275 - accessibilityHint={_(msg`Exits account deletion process`)} 276 - onAccessibilityEscape={onCancel}> 277 - <Text type="button-lg" style={pal.textLight}> 278 - <Trans context="action">Cancel</Trans> 279 - </Text> 280 - </TouchableOpacity> 281 - </> 282 - )} 283 - </> 284 - )} 285 - </ScrollView> 286 - </SafeAreaView> 287 - ) 288 - } 289 - 290 - const styles = StyleSheet.create({ 291 - titleContainer: { 292 - display: 'flex', 293 - flexDirection: 'row', 294 - justifyContent: 'center', 295 - flexWrap: 'wrap', 296 - marginTop: 12, 297 - marginBottom: 12, 298 - marginLeft: 20, 299 - marginRight: 20, 300 - }, 301 - titleMobile: { 302 - textAlign: 'center', 303 - }, 304 - titleDesktop: { 305 - textAlign: 'center', 306 - overflow: 'hidden', 307 - whiteSpace: 'nowrap', 308 - textOverflow: 'ellipsis', 309 - // @ts-ignore only rendered on web 310 - maxWidth: '400px', 311 - }, 312 - description: { 313 - textAlign: 'center', 314 - paddingHorizontal: 22, 315 - marginBottom: 10, 316 - }, 317 - mt20: { 318 - marginTop: 20, 319 - }, 320 - mb20: { 321 - marginBottom: 20, 322 - }, 323 - textInput: { 324 - borderWidth: 1, 325 - borderRadius: 6, 326 - paddingHorizontal: 16, 327 - paddingVertical: 12, 328 - fontSize: 20, 329 - marginHorizontal: 20, 330 - }, 331 - btn: { 332 - flexDirection: 'row', 333 - alignItems: 'center', 334 - justifyContent: 'center', 335 - borderRadius: 32, 336 - padding: 14, 337 - marginHorizontal: 20, 338 - }, 339 - evilBtn: { 340 - backgroundColor: colors.red4, 341 - }, 342 - })
-4
src/view/com/modals/Modal.tsx
··· 7 7 import {useModalControls, useModals} from '#/state/modals' 8 8 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 9 9 import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' 10 - import * as DeleteAccountModal from './DeleteAccount' 11 10 import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' 12 11 import * as UserAddRemoveListsModal from './UserAddRemoveLists' 13 12 ··· 45 44 if (activeModal?.name === 'user-add-remove-lists') { 46 45 snapPoints = UserAddRemoveListsModal.snapPoints 47 46 element = <UserAddRemoveListsModal.Component {...activeModal} /> 48 - } else if (activeModal?.name === 'delete-account') { 49 - snapPoints = DeleteAccountModal.snapPoints 50 - element = <DeleteAccountModal.Component /> 51 47 } else if (activeModal?.name === 'content-languages-settings') { 52 48 snapPoints = ContentLanguagesSettingsModal.snapPoints 53 49 element = <ContentLanguagesSettingsModal.Component />
-3
src/view/com/modals/Modal.web.tsx
··· 6 6 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 7 7 import {type Modal as ModalIface} from '#/state/modals' 8 8 import {useModalControls, useModals} from '#/state/modals' 9 - import * as DeleteAccountModal from './DeleteAccount' 10 9 import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' 11 10 import * as UserAddRemoveLists from './UserAddRemoveLists' 12 11 ··· 48 47 let element 49 48 if (modal.name === 'user-add-remove-lists') { 50 49 element = <UserAddRemoveLists.Component {...modal} /> 51 - } else if (modal.name === 'delete-account') { 52 - element = <DeleteAccountModal.Component /> 53 50 } else if (modal.name === 'content-languages-settings') { 54 51 element = <ContentLanguagesSettingsModal.Component /> 55 52 } else {