new delete account dialog
This commit is contained in:
@@ -3,8 +3,8 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
import {type CommonNavigatorParams} from '#/lib/routes/types'
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
import {DeleteAccountDialog} from '#/screens/Settings/components/DeleteAccountDialog'
|
||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AgeAssuranceAccountCard} from '#/components/ageAssurance/AgeAssuranceAccountCard'
|
import {AgeAssuranceAccountCard} from '#/components/ageAssurance/AgeAssuranceAccountCard'
|
||||||
@@ -34,11 +34,11 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {openModal} = useModalControls()
|
|
||||||
const emailDialogControl = useEmailDialogControl()
|
const emailDialogControl = useEmailDialogControl()
|
||||||
const birthdayControl = useDialogControl()
|
const birthdayControl = useDialogControl()
|
||||||
const changeHandleControl = useDialogControl()
|
const changeHandleControl = useDialogControl()
|
||||||
const changePasswordControl = useDialogControl()
|
const changePasswordControl = useDialogControl()
|
||||||
|
const deleteAccountControl = useDialogControl()
|
||||||
const exportCarControl = useDialogControl()
|
const exportCarControl = useDialogControl()
|
||||||
const deactivateAccountControl = useDialogControl()
|
const deactivateAccountControl = useDialogControl()
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
</SettingsList.PressableItem>
|
</SettingsList.PressableItem>
|
||||||
<SettingsList.PressableItem
|
<SettingsList.PressableItem
|
||||||
label={_(msg`Delete account`)}
|
label={_(msg`Delete account`)}
|
||||||
onPress={() => openModal({name: 'delete-account'})}
|
onPress={() => deleteAccountControl.open()}
|
||||||
destructive>
|
destructive>
|
||||||
<SettingsList.ItemIcon icon={Trash_Stroke2_Corner2_Rounded} />
|
<SettingsList.ItemIcon icon={Trash_Stroke2_Corner2_Rounded} />
|
||||||
<SettingsList.ItemText>
|
<SettingsList.ItemText>
|
||||||
@@ -183,6 +183,10 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
<BirthDateSettingsDialog control={birthdayControl} />
|
<BirthDateSettingsDialog control={birthdayControl} />
|
||||||
<ChangeHandleDialog control={changeHandleControl} />
|
<ChangeHandleDialog control={changeHandleControl} />
|
||||||
<ChangePasswordDialog control={changePasswordControl} />
|
<ChangePasswordDialog control={changePasswordControl} />
|
||||||
|
<DeleteAccountDialog
|
||||||
|
control={deleteAccountControl}
|
||||||
|
deactivateDialogControl={deactivateAccountControl}
|
||||||
|
/>
|
||||||
<ExportCarDialog control={exportCarControl} />
|
<ExportCarDialog control={exportCarControl} />
|
||||||
<DeactivateAccountDialog control={deactivateAccountControl} />
|
<DeactivateAccountDialog control={deactivateAccountControl} />
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import {useCallback, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -7,7 +7,6 @@ import {logger} from '#/logger'
|
|||||||
import {useAgent, useSessionApi} from '#/state/session'
|
import {useAgent, useSessionApi} from '#/state/session'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {type DialogOuterProps} from '#/components/Dialog'
|
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
@@ -17,7 +16,7 @@ import {Text} from '#/components/Typography'
|
|||||||
export function DeactivateAccountDialog({
|
export function DeactivateAccountDialog({
|
||||||
control,
|
control,
|
||||||
}: {
|
}: {
|
||||||
control: DialogOuterProps['control']
|
control: Prompt.PromptControlProps
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Prompt.Outer control={control}>
|
<Prompt.Outer control={control}>
|
||||||
@@ -29,17 +28,17 @@ export function DeactivateAccountDialog({
|
|||||||
function DeactivateAccountDialogInner({
|
function DeactivateAccountDialogInner({
|
||||||
control,
|
control,
|
||||||
}: {
|
}: {
|
||||||
control: DialogOuterProps['control']
|
control: Prompt.PromptControlProps
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const {logoutCurrentAccount} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const [pending, setPending] = React.useState(false)
|
const [pending, setPending] = useState(false)
|
||||||
const [error, setError] = React.useState<string | undefined>()
|
const [error, setError] = useState<string | undefined>()
|
||||||
|
|
||||||
const handleDeactivate = React.useCallback(async () => {
|
const handleDeactivate = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setPending(true)
|
setPending(true)
|
||||||
await agent.com.atproto.server.deactivateAccount({})
|
await agent.com.atproto.server.deactivateAccount({})
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
|
import {Text as RNText, View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
|
||||||
|
import {useAgent, useSession, useSessionApi} from '#/state/session'
|
||||||
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
|
import {Admonition} from '#/components/Admonition'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {resetToTab} from '#/Navigation'
|
||||||
|
|
||||||
|
enum Stages {
|
||||||
|
SendEmail = 'SendEmail',
|
||||||
|
DeleteAccount = 'DeleteAccount',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteAccountDialog({
|
||||||
|
control,
|
||||||
|
deactivateDialogControl,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
deactivateDialogControl: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
<Inner deactivateDialogControl={deactivateDialogControl} />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Inner({
|
||||||
|
deactivateDialogControl,
|
||||||
|
}: {
|
||||||
|
deactivateDialogControl: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const {removeAccount} = useSessionApi()
|
||||||
|
const agent = useAgent()
|
||||||
|
const control = Dialog.useDialogContext()
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
|
const [stage, setStage] = useState(Stages.SendEmail)
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
|
const [confirmCode, setConfirmCode] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
|
const onPressSendEmail = async () => {
|
||||||
|
setError('')
|
||||||
|
setIsProcessing(true)
|
||||||
|
try {
|
||||||
|
await agent.com.atproto.server.requestAccountDelete()
|
||||||
|
setStage(Stages.DeleteAccount)
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(cleanError(e))
|
||||||
|
}
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPressConfirmDelete = async () => {
|
||||||
|
if (!currentAccount?.did) {
|
||||||
|
throw new Error(`DeleteAccount modal: currentAccount.did is undefined`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setError('')
|
||||||
|
setIsProcessing(true)
|
||||||
|
const token = confirmCode.replace(/\s/g, '')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// inform chat service of intent to delete account
|
||||||
|
const {success} = await agent.api.chat.bsky.actor.deleteAccount(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
headers: DM_SERVICE_HEADERS,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('Failed to inform chat service of account deletion')
|
||||||
|
}
|
||||||
|
await agent.com.atproto.server.deleteAccount({
|
||||||
|
did: currentAccount.did,
|
||||||
|
password,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
Toast.show(_(msg`Your account has been deleted`))
|
||||||
|
resetToTab('HomeTab')
|
||||||
|
removeAccount(currentAccount)
|
||||||
|
control.close()
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(cleanError(e))
|
||||||
|
}
|
||||||
|
setIsProcessing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const title =
|
||||||
|
stage === Stages.SendEmail
|
||||||
|
? currentAccount
|
||||||
|
? _(msg`Delete account "${sanitizeHandle(currentAccount?.handle)}"`)
|
||||||
|
: _(msg`Delete account`)
|
||||||
|
: _(msg`Confirm deletion`)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(msg`Delete account dialog`)}
|
||||||
|
style={web({maxWidth: 450})}>
|
||||||
|
<View style={[a.gap_xl]}>
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.font_heavy, a.text_2xl]}>{title}</Text>
|
||||||
|
|
||||||
|
{error ? (
|
||||||
|
<View style={[a.rounded_sm, a.overflow_hidden]}>
|
||||||
|
<ErrorMessage message={error} />
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
|
{stage === Stages.SendEmail ? (
|
||||||
|
<Trans>
|
||||||
|
For security reasons, we'll need to send a confirmation code to
|
||||||
|
your email address.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
Check your inbox for an email with the confirmation code to
|
||||||
|
enter below:
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{stage === Stages.SendEmail && (
|
||||||
|
<Admonition>
|
||||||
|
<Trans>
|
||||||
|
You can also{' '}
|
||||||
|
<RNText
|
||||||
|
style={[{color: t.palette.primary_500}, web(a.underline)]}
|
||||||
|
onPress={() =>
|
||||||
|
control.close(() => deactivateDialogControl.open())
|
||||||
|
}>
|
||||||
|
temporarily deactivate
|
||||||
|
</RNText>{' '}
|
||||||
|
your account instead, and reactivate it at any time.
|
||||||
|
</Trans>
|
||||||
|
</Admonition>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stage === Stages.DeleteAccount && (
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>
|
||||||
|
<Trans>Confirmation Code</Trans>
|
||||||
|
</TextField.LabelText>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Input
|
||||||
|
label={_(msg`Confirmation code`)}
|
||||||
|
placeholder={_(msg`Enter code from email`)}
|
||||||
|
value={confirmCode}
|
||||||
|
onChangeText={setConfirmCode}
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
autoComplete="one-time-code"
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>
|
||||||
|
<Trans>Password</Trans>
|
||||||
|
</TextField.LabelText>
|
||||||
|
<TextField.Root>
|
||||||
|
<TextField.Input
|
||||||
|
label={_(msg`Password`)}
|
||||||
|
placeholder={_(msg`Enter your password`)}
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="password"
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}>
|
||||||
|
{stage === Stages.SendEmail ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Send Email`)}
|
||||||
|
variant="solid"
|
||||||
|
color="negative"
|
||||||
|
size="large"
|
||||||
|
disabled={isProcessing}
|
||||||
|
onPress={onPressSendEmail}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Send Email</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Cancel`)}
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
disabled={isProcessing}
|
||||||
|
onPress={() => control.close()}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : stage === Stages.DeleteAccount ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Delete account`)}
|
||||||
|
variant="solid"
|
||||||
|
color="negative"
|
||||||
|
size="large"
|
||||||
|
disabled={isProcessing}
|
||||||
|
onPress={onPressConfirmDelete}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Delete Account</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
{isProcessing && <ButtonIcon icon={Loader} />}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Cancel`)}
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
disabled={isProcessing}
|
||||||
|
onPress={() => control.close()}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user