Implement account deletion (#141)

This commit is contained in:
Paul Frazee
2023-02-02 18:28:42 -06:00
committed by GitHub
parent 99272c0ec1
commit 9e71a50a1d
10 changed files with 265 additions and 11 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
"@atproto/api": "^0.0.7",
"@atproto/api": "^0.0.8",
"@atproto/lexicon": "^0.0.4",
"@atproto/xrpc": "^0.0.3",
"@bam.tech/react-native-image-resizer": "^3.0.4",
+11 -1
View File
@@ -52,6 +52,14 @@ export class ReportAccountModal {
}
}
export class DeleteAccountModal {
name = 'delete-account'
constructor() {
makeAutoObservable(this)
}
}
interface LightboxModel {}
export class ProfileImageLightbox implements LightboxModel {
@@ -98,6 +106,7 @@ export class ShellUiModel {
| ServerInputModal
| ReportPostModal
| ReportAccountModal
| DeleteAccountModal
| undefined
isLightboxActive = false
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
@@ -140,7 +149,8 @@ export class ShellUiModel {
| EditProfileModal
| ServerInputModal
| ReportPostModal
| ReportAccountModal,
| ReportAccountModal
| DeleteAccountModal,
) {
this.isModalActive = true
this.activeModal = modal
+210
View File
@@ -0,0 +1,210 @@
import React from 'react'
import {
ActivityIndicator,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import {BottomSheetTextInput} from '@gorhom/bottom-sheet'
import LinearGradient from 'react-native-linear-gradient'
import * as Toast from '../util/Toast'
import {Text} from '../util/text/Text'
import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles'
import {usePalette} from '../../lib/hooks/usePalette'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from '../../../lib/strings'
export const snapPoints = ['60%']
export function Component({}: {}) {
const pal = usePalette('default')
const store = useStores()
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [confirmCode, setConfirmCode] = React.useState<string>('')
const [password, setPassword] = React.useState<string>('')
const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
const [error, setError] = React.useState<string>('')
const onPressSendEmail = async () => {
setError('')
setIsProcessing(true)
try {
await store.api.com.atproto.account.requestDelete()
setIsEmailSent(true)
} catch (e: any) {
setError(cleanError(e))
}
setIsProcessing(false)
}
const onPressConfirmDelete = async () => {
setError('')
setIsProcessing(true)
try {
await store.api.com.atproto.account.delete({
did: store.me.did,
password,
token: confirmCode,
})
Toast.show('Your account has been deleted')
store.nav.tab.fixedTabReset()
store.session.clear()
store.shell.closeModal()
} catch (e: any) {
setError(cleanError(e))
}
setIsProcessing(false)
}
const onCancel = () => {
store.shell.closeModal()
}
return (
<View
style={[styles.container, {backgroundColor: pal.colors.backgroundLight}]}>
<View style={[styles.innerContainer, pal.view]}>
<Text type="title-xl" style={styles.title}>
Delete account
</Text>
{!isEmailSent ? (
<>
<Text type="lg" style={styles.description}>
For security reasons, we'll need to send a confirmation code to
your email.
</Text>
{error ? (
<View style={s.mt10}>
<ErrorMessage message={error} />
</View>
) : undefined}
{isProcessing ? (
<View style={[styles.btn, s.mt10]}>
<ActivityIndicator />
</View>
) : (
<>
<TouchableOpacity
style={styles.mt20}
onPress={onPressSendEmail}>
<LinearGradient
colors={[
gradients.blueLight.start,
gradients.blueLight.end,
]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}>
Send email
</Text>
</LinearGradient>
</TouchableOpacity>
<TouchableOpacity
style={[styles.btn, s.mt10]}
onPress={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
</Text>
</TouchableOpacity>
</>
)}
</>
) : (
<>
<Text type="lg" style={styles.description}>
Check your inbox for an email with the confirmation code to enter
below:
</Text>
<BottomSheetTextInput
style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
placeholder="Confirmation code"
placeholderTextColor={pal.textLight.color}
value={confirmCode}
onChangeText={setConfirmCode}
/>
<Text type="lg" style={styles.description}>
Please enter your password as well:
</Text>
<BottomSheetTextInput
style={[styles.textInput, pal.borderDark, pal.text]}
placeholder="Password"
placeholderTextColor={pal.textLight.color}
secureTextEntry
value={password}
onChangeText={setPassword}
/>
{error ? (
<View style={styles.mt20}>
<ErrorMessage message={error} />
</View>
) : undefined}
{isProcessing ? (
<View style={[styles.btn, s.mt10]}>
<ActivityIndicator />
</View>
) : (
<>
<TouchableOpacity
style={[styles.btn, styles.evilBtn, styles.mt20]}
onPress={onPressConfirmDelete}>
<Text type="button-lg" style={[s.white, s.bold]}>
Delete my account
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.btn, s.mt10]}
onPress={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
</Text>
</TouchableOpacity>
</>
)}
</>
)}
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
innerContainer: {
paddingBottom: 20,
},
title: {
textAlign: 'center',
marginTop: 12,
marginBottom: 12,
},
description: {
textAlign: 'center',
paddingHorizontal: 22,
marginBottom: 10,
},
mt20: {
marginTop: 20,
},
mb20: {
marginBottom: 20,
},
textInput: {
borderWidth: 1,
borderRadius: 6,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 20,
marginHorizontal: 20,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 32,
padding: 14,
marginHorizontal: 20,
},
evilBtn: {
backgroundColor: colors.red4,
},
})
+4
View File
@@ -12,6 +12,7 @@ import * as EditProfileModal from './EditProfile'
import * as ServerInputModal from './ServerInput'
import * as ReportPostModal from './ReportPost'
import * as ReportAccountModal from './ReportAccount'
import * as DeleteAccountModal from './DeleteAccount'
import {usePalette} from '../../lib/hooks/usePalette'
import {StyleSheet} from 'react-native'
@@ -76,6 +77,9 @@ export const Modal = observer(function Modal() {
{...(store.shell.activeModal as models.ReportAccountModal)}
/>
)
} else if (store.shell.activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints
element = <DeleteAccountModal.Component />
} else {
element = <View />
}
+2 -2
View File
@@ -107,7 +107,7 @@ export const ProfileHeader = observer(function ProfileHeader({
<LoadingPlaceholder width={100} height={31} style={styles.br50} />
</View>
<View style={styles.displayNameLine}>
<Text type="title-xl" style={[pal.text, styles.title]}>
<Text type="title-2xl" style={[pal.text, styles.title]}>
{view.displayName || view.handle}
</Text>
</View>
@@ -200,7 +200,7 @@ export const ProfileHeader = observer(function ProfileHeader({
) : undefined}
</View>
<View style={styles.displayNameLine}>
<Text type="title-xl" style={[pal.text, styles.title]}>
<Text type="title-2xl" style={[pal.text, styles.title]}>
{view.displayName || view.handle}
</Text>
</View>
+2
View File
@@ -53,6 +53,7 @@ export type TypographyVariant =
| 'xs-medium'
| 'xs-bold'
| 'xs-heavy'
| 'title-2xl'
| 'title-xl'
| 'title-lg'
| 'title'
@@ -60,6 +61,7 @@ export type TypographyVariant =
| 'post-text-lg'
| 'post-text'
| 'button'
| 'button-lg'
| 'mono'
export type Typography = Record<TypographyVariant, TextStyle>
+11 -1
View File
@@ -208,11 +208,16 @@ export const defaultTheme: Theme = {
fontWeight: '800',
},
'title-xl': {
'title-2xl': {
fontSize: 34,
letterSpacing: 0.25,
fontWeight: '500',
},
'title-xl': {
fontSize: 28,
letterSpacing: 0.25,
fontWeight: '500',
},
'title-lg': {
fontSize: 22,
fontWeight: '500',
@@ -237,6 +242,11 @@ export const defaultTheme: Theme = {
letterSpacing: 0.4,
fontWeight: '400',
},
'button-lg': {
fontWeight: '500',
fontSize: 18,
letterSpacing: 0.5,
},
button: {
fontWeight: '500',
fontSize: 14,
+6
View File
@@ -293,6 +293,9 @@ function TypographyView() {
'xs-heavy' lorem ipsum dolor
</Text>
<Text type="title-2xl" style={[pal.text]}>
'title-2xl' lorem ipsum dolor
</Text>
<Text type="title-xl" style={[pal.text]}>
'title-xl' lorem ipsum dolor
</Text>
@@ -305,6 +308,9 @@ function TypographyView() {
<Text type="button" style={[pal.text]}>
Button
</Text>
<Text type="button-lg" style={[pal.text]}>
Button-lg
</Text>
</View>
)
}
+14 -2
View File
@@ -18,6 +18,7 @@ import * as Toast from '../com/util/Toast'
import {UserAvatar} from '../com/util/UserAvatar'
import {usePalette} from '../lib/hooks/usePalette'
import {AccountData} from '../../state/models/session'
import {DeleteAccountModal} from '../../state/models/shell-ui'
export const Settings = observer(function Settings({
navIdx,
@@ -54,6 +55,9 @@ export const Settings = observer(function Settings({
const onPressSignout = () => {
store.session.logout()
}
const onPressDeleteAccount = () => {
store.shell.openModal(new DeleteAccountModal())
}
return (
<View style={[s.h100pct]} testID="settingsScreen">
@@ -146,19 +150,27 @@ export const Settings = observer(function Settings({
</TouchableOpacity>
<View style={styles.spacer} />
<Text type="sm-medium" style={[s.mb5]}>
Danger zone
</Text>
<TouchableOpacity
style={[pal.view, s.p10, s.mb10]}
onPress={onPressDeleteAccount}>
<Text style={pal.textLight}>Delete my account</Text>
</TouchableOpacity>
<Text type="sm-medium" style={[s.mt10, s.mb5]}>
Developer tools
</Text>
<Link
style={[pal.view, s.p10, s.mb2]}
href="/sys/log"
title="System log">
<Text style={pal.link}>System log</Text>
<Text style={pal.textLight}>System log</Text>
</Link>
<Link
style={[pal.view, s.p10, s.mb2]}
href="/sys/debug"
title="Debug tools">
<Text style={pal.link}>Storybook</Text>
<Text style={pal.textLight}>Storybook</Text>
</Link>
<View style={s.footerSpacer} />
</ScrollView>
+4 -4
View File
@@ -19,10 +19,10 @@
jsonpointer "^5.0.0"
leven "^3.1.0"
"@atproto/api@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.0.7.tgz#2cec239dd252a67a768a715e385fa835e834c280"
integrity sha512-2iQ5fd8YG4H5MzcWYtlvemOoXqzhRaxkNblecw1+iopTQjPrrDntJwKIg7+qLW0Mr93w+RSLmmekWUkiIJf1ew==
"@atproto/api@^0.0.8":
version "0.0.8"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.0.8.tgz#ff01bb193cd1dad422916572ff595a625fa3cc06"
integrity sha512-qEnrzy8vKnt3yEjRCmKUnTvW6GKPB9pB5PP1+qIfUnQ7Sqrb793LtaSXPk9ZnrvtL3fsxYnfUY+7KB2X4osSyA==
dependencies:
"@atproto/xrpc" "*"
typed-emitter "^2.1.0"