Stage 11: account linking & merge (email + Telegram Login Widget) (#12)
This commit was merged in pull request #12.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Modal from '../components/Modal.svelte';
|
||||
import Screen from '../components/Screen.svelte';
|
||||
import { app, handleError, logout, showToast } from '../lib/app.svelte';
|
||||
import { app, applyLinkResult, handleError, logout, showToast } from '../lib/app.svelte';
|
||||
import { gateway } from '../lib/gateway';
|
||||
import { loginWidgetAvailable, requestTelegramLogin } from '../lib/telegram';
|
||||
import { t } from '../lib/i18n/index.svelte';
|
||||
import {
|
||||
awayDurationOk,
|
||||
@@ -29,6 +31,11 @@
|
||||
let emailInput = $state('');
|
||||
let codeInput = $state('');
|
||||
let emailSent = $state(false);
|
||||
// A pending irreversible merge surfaced after the code/widget was verified; the
|
||||
// dialog confirms it. tgData holds the Telegram widget payload for the merge step.
|
||||
let pendingMerge = $state<null | { kind: 'email' | 'telegram'; name: string; games: number; friends: number }>(null);
|
||||
let tgData = '';
|
||||
const telegramLinkable = loginWidgetAvailable();
|
||||
|
||||
function defaultTz(): string {
|
||||
const b = browserOffset();
|
||||
@@ -79,10 +86,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function resetEmail() {
|
||||
emailSent = false;
|
||||
emailInput = '';
|
||||
codeInput = '';
|
||||
}
|
||||
|
||||
async function requestEmail() {
|
||||
if (!emailOk) return;
|
||||
try {
|
||||
await gateway.emailBindRequest(emailInput.trim());
|
||||
await gateway.linkEmailRequest(emailInput.trim());
|
||||
emailSent = true;
|
||||
showToast(t('profile.emailSent', { email: emailInput.trim() }));
|
||||
} catch (e) {
|
||||
@@ -92,11 +105,48 @@
|
||||
|
||||
async function confirmEmail() {
|
||||
try {
|
||||
app.profile = await gateway.emailBindConfirm(emailInput.trim(), codeInput.trim());
|
||||
emailSent = false;
|
||||
emailInput = '';
|
||||
codeInput = '';
|
||||
showToast(t('profile.emailBound'));
|
||||
const r = await gateway.linkEmailConfirm(emailInput.trim(), codeInput.trim());
|
||||
if (r.status === 'merge_required') {
|
||||
pendingMerge = { kind: 'email', name: r.secondaryDisplayName, games: r.secondaryGames, friends: r.secondaryFriends };
|
||||
return;
|
||||
}
|
||||
await applyLinkResult(r);
|
||||
resetEmail();
|
||||
showToast(t('profile.linked'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function linkTelegram() {
|
||||
try {
|
||||
const data = await requestTelegramLogin();
|
||||
if (!data) return;
|
||||
const r = await gateway.linkTelegram(data);
|
||||
if (r.status === 'merge_required') {
|
||||
tgData = data;
|
||||
pendingMerge = { kind: 'telegram', name: r.secondaryDisplayName, games: r.secondaryGames, friends: r.secondaryFriends };
|
||||
return;
|
||||
}
|
||||
await applyLinkResult(r);
|
||||
showToast(t('profile.linked'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmMerge() {
|
||||
if (!pendingMerge) return;
|
||||
try {
|
||||
const r =
|
||||
pendingMerge.kind === 'email'
|
||||
? await gateway.linkEmailMerge(emailInput.trim(), codeInput.trim())
|
||||
: await gateway.linkTelegramMerge(tgData);
|
||||
await applyLinkResult(r);
|
||||
pendingMerge = null;
|
||||
tgData = '';
|
||||
resetEmail();
|
||||
showToast(t('profile.merged'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
@@ -169,38 +219,54 @@
|
||||
<p class="muted">{t('profile.guestLocked')}</p>
|
||||
{:else}
|
||||
<button class="btn" onclick={startEdit}>{t('profile.edit')}</button>
|
||||
|
||||
<section class="emailbox">
|
||||
<h3>{t('profile.bindEmail')}</h3>
|
||||
{#if !emailSent}
|
||||
<div class="addrow">
|
||||
<input
|
||||
class:invalid={emailInput.length > 0 && !emailOk}
|
||||
bind:value={emailInput}
|
||||
placeholder={t('login.emailPlaceholder')}
|
||||
type="email"
|
||||
/>
|
||||
<button class="ghost" onclick={requestEmail} disabled={!emailOk}>{t('login.sendCode')}</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="addrow">
|
||||
<input
|
||||
class="codein"
|
||||
bind:value={codeInput}
|
||||
placeholder={t('profile.emailCode')}
|
||||
inputmode="numeric"
|
||||
maxlength="6"
|
||||
/>
|
||||
<button class="btn" onclick={confirmEmail}>{t('common.ok')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Linking & merge (Stage 11). Shown to everyone, including guests, who
|
||||
upgrade by binding their first identity. -->
|
||||
<section class="emailbox">
|
||||
<h3>{t('profile.linkAccount')}</h3>
|
||||
{#if !emailSent}
|
||||
<div class="addrow">
|
||||
<input
|
||||
class:invalid={emailInput.length > 0 && !emailOk}
|
||||
bind:value={emailInput}
|
||||
placeholder={t('login.emailPlaceholder')}
|
||||
type="email"
|
||||
/>
|
||||
<button class="ghost" onclick={requestEmail} disabled={!emailOk}>{t('login.sendCode')}</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="addrow">
|
||||
<input
|
||||
class="codein"
|
||||
bind:value={codeInput}
|
||||
placeholder={t('profile.emailCode')}
|
||||
inputmode="numeric"
|
||||
maxlength="6"
|
||||
/>
|
||||
<button class="btn" onclick={confirmEmail}>{t('common.ok')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if telegramLinkable}
|
||||
<button class="ghost tg" onclick={linkTelegram}>{t('profile.linkTelegram')}</button>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<button class="logout" onclick={() => logout()}>{t('login.title')} / logout</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if pendingMerge}
|
||||
<Modal title={t('profile.mergeTitle')} onclose={() => (pendingMerge = null)}>
|
||||
<p>{t('profile.mergeBody', { name: pendingMerge.name, games: pendingMerge.games, friends: pendingMerge.friends })}</p>
|
||||
<p class="warn">{t('profile.mergeIrreversible')}</p>
|
||||
<div class="addrow end">
|
||||
<button class="ghost" onclick={() => (pendingMerge = null)}>{t('common.cancel')}</button>
|
||||
<button class="btn" onclick={confirmMerge}>{t('profile.mergeConfirm')}</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
</Screen>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user