Implement account deletion (#141)
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.0.7",
|
"@atproto/api": "^0.0.8",
|
||||||
"@atproto/lexicon": "^0.0.4",
|
"@atproto/lexicon": "^0.0.4",
|
||||||
"@atproto/xrpc": "^0.0.3",
|
"@atproto/xrpc": "^0.0.3",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@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 {}
|
interface LightboxModel {}
|
||||||
|
|
||||||
export class ProfileImageLightbox implements LightboxModel {
|
export class ProfileImageLightbox implements LightboxModel {
|
||||||
@@ -98,6 +106,7 @@ export class ShellUiModel {
|
|||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| ReportPostModal
|
| ReportPostModal
|
||||||
| ReportAccountModal
|
| ReportAccountModal
|
||||||
|
| DeleteAccountModal
|
||||||
| undefined
|
| undefined
|
||||||
isLightboxActive = false
|
isLightboxActive = false
|
||||||
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
|
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
|
||||||
@@ -140,7 +149,8 @@ export class ShellUiModel {
|
|||||||
| EditProfileModal
|
| EditProfileModal
|
||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| ReportPostModal
|
| ReportPostModal
|
||||||
| ReportAccountModal,
|
| ReportAccountModal
|
||||||
|
| DeleteAccountModal,
|
||||||
) {
|
) {
|
||||||
this.isModalActive = true
|
this.isModalActive = true
|
||||||
this.activeModal = modal
|
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 ServerInputModal from './ServerInput'
|
||||||
import * as ReportPostModal from './ReportPost'
|
import * as ReportPostModal from './ReportPost'
|
||||||
import * as ReportAccountModal from './ReportAccount'
|
import * as ReportAccountModal from './ReportAccount'
|
||||||
|
import * as DeleteAccountModal from './DeleteAccount'
|
||||||
import {usePalette} from '../../lib/hooks/usePalette'
|
import {usePalette} from '../../lib/hooks/usePalette'
|
||||||
import {StyleSheet} from 'react-native'
|
import {StyleSheet} from 'react-native'
|
||||||
|
|
||||||
@@ -76,6 +77,9 @@ export const Modal = observer(function Modal() {
|
|||||||
{...(store.shell.activeModal as models.ReportAccountModal)}
|
{...(store.shell.activeModal as models.ReportAccountModal)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
} else if (store.shell.activeModal?.name === 'delete-account') {
|
||||||
|
snapPoints = DeleteAccountModal.snapPoints
|
||||||
|
element = <DeleteAccountModal.Component />
|
||||||
} else {
|
} else {
|
||||||
element = <View />
|
element = <View />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||||||
<LoadingPlaceholder width={100} height={31} style={styles.br50} />
|
<LoadingPlaceholder width={100} height={31} style={styles.br50} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.displayNameLine}>
|
<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}
|
{view.displayName || view.handle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -200,7 +200,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||||||
) : undefined}
|
) : undefined}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.displayNameLine}>
|
<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}
|
{view.displayName || view.handle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export type TypographyVariant =
|
|||||||
| 'xs-medium'
|
| 'xs-medium'
|
||||||
| 'xs-bold'
|
| 'xs-bold'
|
||||||
| 'xs-heavy'
|
| 'xs-heavy'
|
||||||
|
| 'title-2xl'
|
||||||
| 'title-xl'
|
| 'title-xl'
|
||||||
| 'title-lg'
|
| 'title-lg'
|
||||||
| 'title'
|
| 'title'
|
||||||
@@ -60,6 +61,7 @@ export type TypographyVariant =
|
|||||||
| 'post-text-lg'
|
| 'post-text-lg'
|
||||||
| 'post-text'
|
| 'post-text'
|
||||||
| 'button'
|
| 'button'
|
||||||
|
| 'button-lg'
|
||||||
| 'mono'
|
| 'mono'
|
||||||
export type Typography = Record<TypographyVariant, TextStyle>
|
export type Typography = Record<TypographyVariant, TextStyle>
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -208,11 +208,16 @@ export const defaultTheme: Theme = {
|
|||||||
fontWeight: '800',
|
fontWeight: '800',
|
||||||
},
|
},
|
||||||
|
|
||||||
'title-xl': {
|
'title-2xl': {
|
||||||
fontSize: 34,
|
fontSize: 34,
|
||||||
letterSpacing: 0.25,
|
letterSpacing: 0.25,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
'title-xl': {
|
||||||
|
fontSize: 28,
|
||||||
|
letterSpacing: 0.25,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
'title-lg': {
|
'title-lg': {
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
@@ -237,6 +242,11 @@ export const defaultTheme: Theme = {
|
|||||||
letterSpacing: 0.4,
|
letterSpacing: 0.4,
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
|
'button-lg': {
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: 18,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
button: {
|
button: {
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
@@ -293,6 +293,9 @@ function TypographyView() {
|
|||||||
'xs-heavy' lorem ipsum dolor
|
'xs-heavy' lorem ipsum dolor
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Text type="title-2xl" style={[pal.text]}>
|
||||||
|
'title-2xl' lorem ipsum dolor
|
||||||
|
</Text>
|
||||||
<Text type="title-xl" style={[pal.text]}>
|
<Text type="title-xl" style={[pal.text]}>
|
||||||
'title-xl' lorem ipsum dolor
|
'title-xl' lorem ipsum dolor
|
||||||
</Text>
|
</Text>
|
||||||
@@ -305,6 +308,9 @@ function TypographyView() {
|
|||||||
<Text type="button" style={[pal.text]}>
|
<Text type="button" style={[pal.text]}>
|
||||||
Button
|
Button
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text type="button-lg" style={[pal.text]}>
|
||||||
|
Button-lg
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import * as Toast from '../com/util/Toast'
|
|||||||
import {UserAvatar} from '../com/util/UserAvatar'
|
import {UserAvatar} from '../com/util/UserAvatar'
|
||||||
import {usePalette} from '../lib/hooks/usePalette'
|
import {usePalette} from '../lib/hooks/usePalette'
|
||||||
import {AccountData} from '../../state/models/session'
|
import {AccountData} from '../../state/models/session'
|
||||||
|
import {DeleteAccountModal} from '../../state/models/shell-ui'
|
||||||
|
|
||||||
export const Settings = observer(function Settings({
|
export const Settings = observer(function Settings({
|
||||||
navIdx,
|
navIdx,
|
||||||
@@ -54,6 +55,9 @@ export const Settings = observer(function Settings({
|
|||||||
const onPressSignout = () => {
|
const onPressSignout = () => {
|
||||||
store.session.logout()
|
store.session.logout()
|
||||||
}
|
}
|
||||||
|
const onPressDeleteAccount = () => {
|
||||||
|
store.shell.openModal(new DeleteAccountModal())
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[s.h100pct]} testID="settingsScreen">
|
<View style={[s.h100pct]} testID="settingsScreen">
|
||||||
@@ -146,19 +150,27 @@ export const Settings = observer(function Settings({
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={styles.spacer} />
|
<View style={styles.spacer} />
|
||||||
<Text type="sm-medium" style={[s.mb5]}>
|
<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
|
Developer tools
|
||||||
</Text>
|
</Text>
|
||||||
<Link
|
<Link
|
||||||
style={[pal.view, s.p10, s.mb2]}
|
style={[pal.view, s.p10, s.mb2]}
|
||||||
href="/sys/log"
|
href="/sys/log"
|
||||||
title="System log">
|
title="System log">
|
||||||
<Text style={pal.link}>System log</Text>
|
<Text style={pal.textLight}>System log</Text>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
style={[pal.view, s.p10, s.mb2]}
|
style={[pal.view, s.p10, s.mb2]}
|
||||||
href="/sys/debug"
|
href="/sys/debug"
|
||||||
title="Debug tools">
|
title="Debug tools">
|
||||||
<Text style={pal.link}>Storybook</Text>
|
<Text style={pal.textLight}>Storybook</Text>
|
||||||
</Link>
|
</Link>
|
||||||
<View style={s.footerSpacer} />
|
<View style={s.footerSpacer} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
jsonpointer "^5.0.0"
|
jsonpointer "^5.0.0"
|
||||||
leven "^3.1.0"
|
leven "^3.1.0"
|
||||||
|
|
||||||
"@atproto/api@^0.0.7":
|
"@atproto/api@^0.0.8":
|
||||||
version "0.0.7"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.0.7.tgz#2cec239dd252a67a768a715e385fa835e834c280"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.0.8.tgz#ff01bb193cd1dad422916572ff595a625fa3cc06"
|
||||||
integrity sha512-2iQ5fd8YG4H5MzcWYtlvemOoXqzhRaxkNblecw1+iopTQjPrrDntJwKIg7+qLW0Mr93w+RSLmmekWUkiIJf1ew==
|
integrity sha512-qEnrzy8vKnt3yEjRCmKUnTvW6GKPB9pB5PP1+qIfUnQ7Sqrb793LtaSXPk9ZnrvtL3fsxYnfUY+7KB2X4osSyA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/xrpc" "*"
|
"@atproto/xrpc" "*"
|
||||||
typed-emitter "^2.1.0"
|
typed-emitter "^2.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user