feat(profile): account-deletion flow + terminal deleted screen

Profile gains a Delete-account control (durable accounts) opening a step-up dialog: a
mailed code for an email account, or the typed DELETE phrase for a platform-only one.
On success the app swaps to a terminal AccountDeleted screen ('Учётная запись удалена')
with a Close that closes the host Mini App (telegramClose / vkClose; web = no close).
Wires deleteRequest/deleteConfirm through client/transport/mock/codec; ru/en i18n;
codec wire test + Chromium/WebKit e2e.
This commit is contained in:
Ilia Denisov
2026-07-03 13:18:37 +02:00
parent aa2290b7b4
commit cabcd94d92
14 changed files with 267 additions and 2 deletions
+39 -1
View File
@@ -13,6 +13,7 @@ import { startLocalEvalMetrics } from './localeval-metrics';
import { applyReduceMotion, applyTelegramTheme, applyTheme, type ThemePref, type TelegramThemeParams } from './theme';
import {
insideTelegram,
telegramClose,
collectTelegramDiag,
type TelegramDiag,
onTelegramPath,
@@ -32,7 +33,7 @@ import {
telegramCloudGet,
telegramCloudSet,
} from './telegram';
import { onVKPath, insideVK, vkInit, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets, vkSetViewSettings, appearanceForBg } from './vk';
import { onVKPath, insideVK, vkInit, vkClose, vkDisableSwipeBack, vkLaunchParams, vkUserName, vkStartParam, vkOnScheme, vkOnInsets, vkSetViewSettings, appearanceForBg } from './vk';
import { haptic } from './haptics';
import { CLOUD_PREFS_KEY, decodeClientPrefs, encodeClientPrefs } from './cloudprefs';
import { parseStartParam } from './deeplink';
@@ -92,6 +93,9 @@ export const app = $state<{
/** The caller's active manual block, or null. When set, App.svelte replaces every screen with
* the terminal blocked screen and all push/poll is stopped. */
blocked: BlockStatus | null;
/** Set once the account has been deleted: App.svelte shows the terminal "account deleted"
* screen and all push/poll is stopped. Reopening the app just creates a fresh account. */
accountDeleted: boolean;
toast: Toast | null;
lastEvent: PushEvent | null;
theme: ThemePref;
@@ -140,6 +144,7 @@ export const app = $state<{
session: null,
profile: null,
blocked: null,
accountDeleted: false,
toast: null,
lastEvent: null,
theme: 'auto',
@@ -544,6 +549,39 @@ export async function applyLinkResult(r: LinkResult): Promise<void> {
void persistLanguageToServer(app.locale);
}
/**
* requestDeleteAccount starts account deletion and reports which step-up the account uses:
* 'email' (a code was mailed) or 'phrase' (type the confirmation phrase).
*/
export async function requestDeleteAccount(): Promise<'email' | 'phrase'> {
return (await gateway.deleteRequest()).method;
}
/**
* confirmDeleteAccount confirms deletion with the mailed code or the typed phrase. On
* success it switches to the terminal "account deleted" screen and stops all push/poll; it
* throws on a bad code/phrase so the caller can let the user retry.
*/
export async function confirmDeleteAccount(code: string, phrase: string): Promise<void> {
await gateway.deleteConfirm(code, phrase);
closeStream();
app.accountDeleted = true;
}
/**
* closeDeletedApp closes the host Mini App from the terminal deleted screen (Telegram / VK).
* A plain browser has nothing to close, so it is a no-op there — the terminal screen stays.
*/
export function closeDeletedApp(): void {
if (insideTelegram()) {
telegramClose();
return;
}
if (insideVK()) {
void vkClose();
}
}
/**
* syncTelegramChrome paints Telegram's header/background/bottom bar from the app's live
* theme tokens, so the surrounding chrome matches the UI. Called after the theme is applied.