fix(ui): localise the confirm screen, drop the brand on the error state
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m3s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m3s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
The session-less /confirm page defaulted to English, so it showed the English app title. Carry the recipient's language on the deeplink (?lang) and setLocale on load so the page matches the email. Show a localised brand wordmark (Эрудит / Erudit, matching the email) on the success state only; the invalid/expired state now shows just the message, no header.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
export const en = {
|
||||
'app.title': 'Scrabble',
|
||||
'app.brand': 'Erudit',
|
||||
'dict.loading': 'Loading…',
|
||||
'connection.connecting': 'Connecting…',
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { MessageKey } from './en';
|
||||
|
||||
export const ru: Record<MessageKey, string> = {
|
||||
'app.title': 'Scrabble',
|
||||
'app.brand': 'Эрудит',
|
||||
'dict.loading': 'Загрузка…',
|
||||
'connection.connecting': 'Подключение…',
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { confirmDeeplink } from '../lib/app.svelte';
|
||||
import { t } from '../lib/i18n/index.svelte';
|
||||
import { setLocale, t } from '../lib/i18n/index.svelte';
|
||||
|
||||
let { token }: { token: string } = $props();
|
||||
// The token rides the URL fragment (#/confirm/<token>), which is never sent to the
|
||||
// server, so a plain link prefetch cannot reach it — confirm as soon as the screen
|
||||
// loads. The manual 6-digit code in the same email is the fallback if an aggressive
|
||||
// scanner ever runs the page and consumes the single-use token first.
|
||||
// busy → (a login navigates into the app) | linked | merge | error. The token rides
|
||||
// the URL fragment (#/confirm/<token>), which is never sent to the server, so a plain
|
||||
// link prefetch cannot reach it — confirm on load. The manual six-digit code in the
|
||||
// same email is the fallback if an aggressive scanner runs the page first.
|
||||
let stage = $state<'busy' | 'linked' | 'merge' | 'error'>('busy');
|
||||
|
||||
onMount(async () => {
|
||||
// The email carries the recipient's language on the link (?lang), so this
|
||||
// session-less page renders in it rather than the default.
|
||||
const query = window.location.hash.split('?')[1] ?? '';
|
||||
const lang = new URLSearchParams(query).get('lang');
|
||||
if (lang === 'ru' || lang === 'en') setLocale(lang);
|
||||
|
||||
if (!token) {
|
||||
stage = 'error';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// A login navigates into the app; a link stays here with a result.
|
||||
const r = await confirmDeeplink(token);
|
||||
stage = r === 'merge_required' ? 'merge' : 'linked';
|
||||
} catch {
|
||||
@@ -27,17 +32,14 @@
|
||||
|
||||
<main class="confirm">
|
||||
<div class="card">
|
||||
<div class="brand">{t('app.title')}</div>
|
||||
{#if stage === 'busy'}
|
||||
<p class="muted">{t('confirm.busy')}</p>
|
||||
{:else if stage === 'linked'}
|
||||
<p class="ok">{t('confirm.linked')}</p>
|
||||
<p class="muted">{t('confirm.close')}</p>
|
||||
{:else if stage === 'merge'}
|
||||
<p class="ok">{t('confirm.merge')}</p>
|
||||
<p class="muted">{t('confirm.close')}</p>
|
||||
{:else}
|
||||
{:else if stage === 'error'}
|
||||
<p class="err">{t('confirm.expired')}</p>
|
||||
{:else}
|
||||
<div class="brand">{t('app.brand')}</div>
|
||||
<p class="ok">{stage === 'merge' ? t('confirm.merge') : t('confirm.linked')}</p>
|
||||
<p class="muted">{t('confirm.close')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user