Bluesky app fork with some witchin' additions 馃挮
at readme-update 348 lines 12 kB view raw
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})