Files
bsky-social-app/src/screens/Settings/components/DeactivateAccountDialog.tsx
T
Samuel Newman f1afc3c47c match app-password errors on the typed lex error, not raw message equality
isErrorMaybeAppPasswordPermissions compared getErrorName against
'TokenInvalid', a code the PDS never sends - the string fallback was
doing all the work. The real wire shape is the GENERIC 'InvalidToken'
code (also used for malformed/expired tokens) with the app-password
specifics only in the message, so the typed path now matches code AND
message ('Bad token scope' / 'Bad token method').

Deactivated and DeactivateAccountDialog used raw switch(e.message)
equality; both now go through the helper. Tests updated to pin the
actual PDS wire shape (and reject plain malformed-token errors).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:43 +03:00

129 lines
3.8 KiB
TypeScript

import {useCallback, useState} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {isErrorMaybeAppPasswordPermissions} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {usePdsClient, useSessionApi} from '#/state/session'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {type DialogOuterProps} from '#/components/Dialog'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
import {com} from '#/lexicons'
export function DeactivateAccountDialog({
control,
}: {
control: DialogOuterProps['control']
}) {
return (
<Prompt.Outer control={control}>
<DeactivateAccountDialogInner control={control} />
</Prompt.Outer>
)
}
function DeactivateAccountDialogInner({
control,
}: {
control: DialogOuterProps['control']
}) {
const t = useTheme()
const {_} = useLingui()
const pdsClient = usePdsClient()
const {logoutCurrentAccount} = useSessionApi()
const [pending, setPending] = useState(false)
const [error, setError] = useState<string | undefined>()
const handleDeactivate = useCallback(async () => {
try {
setPending(true)
await pdsClient.call(com.atproto.server.deactivateAccount, {})
control.close(() => {
logoutCurrentAccount('Deactivated')
})
} catch (e: any) {
if (isErrorMaybeAppPasswordPermissions(e)) {
setError(
_(
msg`You're signed in with an App Password. Please sign in with your main password to continue deactivating your account.`,
),
)
} else {
setError(_(msg`Something went wrong, please try again`))
}
logger.error(e, {
message: 'Failed to deactivate account',
})
} finally {
setPending(false)
}
}, [pdsClient, control, logoutCurrentAccount, _, setPending])
return (
<>
<Prompt.TitleText>{_(msg`Deactivate account`)}</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
Your profile, posts, feeds, and lists will no longer be visible to
other Bluesky users. You can reactivate your account at any time by
logging in.
</Trans>
</Prompt.DescriptionText>
<View style={[a.pb_xl]}>
<Divider />
<View style={[a.gap_sm, a.pt_lg, a.pb_xl]}>
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
<Trans>
There is no time limit for account deactivation, come back any
time.
</Trans>
</Text>
<Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
<Trans>
If you're trying to change your handle or email, do so before you
deactivate.
</Trans>
</Text>
</View>
<Divider />
</View>
<Prompt.Actions>
<Button
color="negative"
size="large"
label={_(msg`Yes, deactivate`)}
onPress={handleDeactivate}>
<ButtonText>{_(msg`Yes, deactivate`)}</ButtonText>
{pending && <ButtonIcon icon={Loader} position="right" />}
</Button>
<Prompt.Cancel />
</Prompt.Actions>
{error && (
<View
style={[
a.flex_row,
a.gap_sm,
a.mt_md,
a.p_md,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo size="md" fill={t.palette.negative_400} />
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</View>
)}
</>
)
}