Implement account deletion (#141)
This commit is contained in:
+1
-1
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
@@ -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 />
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user