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:
@@ -1,7 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Modal from '../components/Modal.svelte';
|
||||
import { app, applyLinkResult, handleError, logout, showToast } from '../lib/app.svelte';
|
||||
import {
|
||||
app,
|
||||
applyLinkResult,
|
||||
confirmDeleteAccount,
|
||||
handleError,
|
||||
logout,
|
||||
requestDeleteAccount,
|
||||
showToast,
|
||||
} from '../lib/app.svelte';
|
||||
import { GatewayError } from '../lib/client';
|
||||
import { connection } from '../lib/connection.svelte';
|
||||
import { gateway } from '../lib/gateway';
|
||||
@@ -43,6 +51,10 @@
|
||||
let changingEmail = $state(false);
|
||||
// A pending unlink awaiting the confirmation dialog (email is never unlinked).
|
||||
let confirmUnlink = $state<null | { kind: 'telegram' | 'vk'; label: string }>(null);
|
||||
// The account-deletion step-up dialog: null when closed, else the required proof method.
|
||||
let deleting = $state<null | { method: 'email' | 'phrase' }>(null);
|
||||
let deleteCode = $state('');
|
||||
let deletePhrase = $state('');
|
||||
const telegramLinkable = loginWidgetAvailable();
|
||||
|
||||
function defaultTz(): string {
|
||||
@@ -235,6 +247,29 @@
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// startDelete opens the deletion dialog, asking the backend which step-up the account uses
|
||||
// (a mailed code for an email account, or a typed phrase for a platform-only one).
|
||||
async function startDelete() {
|
||||
try {
|
||||
const method = await requestDeleteAccount();
|
||||
deleteCode = '';
|
||||
deletePhrase = '';
|
||||
deleting = { method };
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
if (!deleting) return;
|
||||
try {
|
||||
// On success the app swaps to the terminal "account deleted" screen (app.accountDeleted).
|
||||
await confirmDeleteAccount(deleteCode.trim(), deletePhrase.trim());
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
@@ -381,6 +416,10 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if !p.isGuest}
|
||||
<button class="deletebtn" onclick={startDelete} disabled={!connection.online}>{t('profile.deleteAccount')}</button>
|
||||
{/if}
|
||||
|
||||
<!-- Logout is hidden for now but kept wired — drop `hidden` to re-enable
|
||||
once its entry point is decided; logout() also still runs on an invalid session. -->
|
||||
<button class="logout" hidden onclick={() => logout()}>{t('login.title')} / logout</button>
|
||||
@@ -409,6 +448,28 @@
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
{#if deleting}
|
||||
{@const d = deleting}
|
||||
<Modal title={t('profile.deleteTitle')} onclose={() => (deleting = null)}>
|
||||
<p class="warn">{t('profile.deleteWarn')}</p>
|
||||
{#if d.method === 'email'}
|
||||
<p class="muted">{t('profile.deleteEmailPrompt')}</p>
|
||||
<div class="addrow">
|
||||
<input class="codein" bind:value={deleteCode} placeholder={t('profile.emailCode')} inputmode="numeric" maxlength="6" />
|
||||
</div>
|
||||
{:else}
|
||||
<p class="muted">{t('profile.deletePhrasePrompt')}</p>
|
||||
<div class="addrow">
|
||||
<input bind:value={deletePhrase} placeholder={t('profile.deletePhrasePlaceholder')} autocapitalize="characters" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="addrow end">
|
||||
<button class="ghost" onclick={() => (deleting = null)}>{t('common.cancel')}</button>
|
||||
<button class="btn danger" onclick={doDelete} disabled={!connection.online}>{t('profile.deleteConfirm')}</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.page {
|
||||
padding: var(--pad);
|
||||
@@ -614,6 +675,18 @@
|
||||
.ghost:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.deletebtn {
|
||||
margin-top: 8px;
|
||||
align-self: flex-start;
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--danger, #c0392b);
|
||||
background: transparent;
|
||||
color: var(--danger, #c0392b);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.deletebtn:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.logout {
|
||||
margin-top: 8px;
|
||||
align-self: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user