import {useCallback, useRef, useState} from 'react'
import {type TextInput, View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {DM_SERVICE_HEADERS} from '#/lib/constants'
import {useCleanError} from '#/lib/hooks/useCleanError'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {useAgent, useSession, useSessionApi} from '#/state/session'
import {atoms as a, useTheme, web} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {type DialogOuterProps} from '#/components/Dialog'
import {
isValidCode,
TokenField,
} from '#/components/dialogs/EmailDialog/components/TokenField'
import * as TextField from '#/components/forms/TextField'
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
import {createStaticClick, InlineLinkText} from '#/components/Link'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import * as toast from '#/components/Toast'
import {Span, Text} from '#/components/Typography'
import {resetToTab} from '#/Navigation'
const WHITESPACE_RE = /\s/gu
const PASSWORD_MIN_LENGTH = 8
enum Step {
SEND_CODE,
VERIFY_CODE,
CONFIRM_DELETION,
}
enum EmailState {
DEFAULT,
PENDING,
}
function isPasswordValid(password: string) {
return password.length >= PASSWORD_MIN_LENGTH
}
export function DeleteAccountDialog({
control,
deactivateDialogControl,
}: {
control: DialogOuterProps['control']
deactivateDialogControl: DialogOuterProps['control']
}) {
return (
)
}
function DeleteAccountDialogInner({
control,
deactivateDialogControl,
}: {
control: DialogOuterProps['control']
deactivateDialogControl: DialogOuterProps['control']
}) {
const passwordRef = useRef(null)
const t = useTheme()
const {_} = useLingui()
const cleanError = useCleanError()
const agent = useAgent()
const {currentAccount} = useSession()
const {removeAccount} = useSessionApi()
const [emailState, setEmailState] = useState(EmailState.DEFAULT)
const [emailSentCount, setEmailSentCount] = useState(0)
const [step, setStep] = useState(Step.SEND_CODE)
const [confirmCode, setConfirmCode] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const sendEmail = useCallback(async () => {
if (emailState === EmailState.PENDING) {
return
}
try {
setEmailState(EmailState.PENDING)
await agent.com.atproto.server.requestAccountDelete()
setError('')
setEmailSentCount(prevCount => prevCount + 1)
setStep(Step.VERIFY_CODE)
} catch (e: any) {
const {clean, raw} = cleanError(e)
const error = clean || raw || e
setError(error)
logger.error(raw || e, {
message: 'Failed to send account deletion verification email',
})
} finally {
setEmailState(EmailState.DEFAULT)
}
}, [agent, cleanError, emailState, setEmailState])
const confirmDeletion = useCallback(async () => {
try {
setError('')
if (!currentAccount?.did) {
throw new Error('Invalid did')
}
const token = confirmCode.replace(WHITESPACE_RE, '')
// 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,
})
control.close(() => {
toast.show(_(msg`Your account has been deleted, see ya! ✌️`))
resetToTab('HomeTab')
removeAccount(currentAccount)
})
} catch (e: any) {
const {clean, raw} = cleanError(e)
const error = clean || raw || e
setError(error)
logger.error(raw || e, {
message: 'Failed to delete account',
})
setConfirmCode('')
setPassword('')
setStep(Step.VERIFY_CODE)
}
}, [
_,
agent,
cleanError,
confirmCode,
control,
currentAccount,
password,
removeAccount,
])
const handleDeactivate = useCallback(() => {
control.close(() => deactivateDialogControl.open())
}, [control, deactivateDialogControl])
const handleSendEmail = useCallback(() => {
void sendEmail()
}, [sendEmail])
const handleSubmitConfirmCode = useCallback(() => {
passwordRef.current?.focus()
}, [])
const handleDeleteAccount = useCallback(() => {
setStep(Step.CONFIRM_DELETION)
}, [setStep])
const handleConfirmDeletion = useCallback(() => {
void confirmDeletion()
}, [confirmDeletion])
const currentHandle = sanitizeHandle(currentAccount?.handle ?? '', '@')
const currentEmail = currentAccount?.email ?? '(no email)'
switch (step) {
case Step.SEND_CODE:
return (
<>
{_(msg`Delete account “${currentHandle}”`)}
For security reasons, we’ll need to send a confirmation code to
your email address{' '}
{currentEmail}
.
{error && (
{error}
)}
You can also{' '}
temporarily deactivate
{' '}
your account instead. 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.
>
)
case Step.VERIFY_CODE:
return (
<>
{_(msg`Delete account “${currentHandle}”`)}
Check{' '}
{currentEmail}
{' '}
for an email with the confirmation code to enter below:
Confirmation code
{emailSentCount > 1 ? (
Email sent!{' '}
{
void handleSendEmail()
})}>
Click here to resend.
) : (
Don’t see a code?{' '}
{
void handleSendEmail()
})}>
Click here to resend.
)}{' '}
{emailState === EmailState.PENDING ? : null}
Password
{error && (
{error}
)}
>
)
case Step.CONFIRM_DELETION:
return (
<>
{_(msg`Are you really, really sure?`)}
This will irreversibly delete your Bluesky account{' '}
{currentHandle}
{' '}
and all associated data. Note that this will affect any other{' '}
AT Protocol
{' '}
services you use with this account.
>
)
}
}