Wire locale catalogs, repair mint terminology, and confirm login with a toast.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
michilis
2026-08-24 22:35:43 +02:00
co-authored by Cursor
parent b95aab2bcd
commit 301679d340
33 changed files with 1570 additions and 963 deletions
+51 -4
View File
@@ -14,6 +14,7 @@ import { escapeHtml, iconGradient, shortNpub, truncateMiddle, wireCopyButtons }
import { useI18n } from '../i18n/client';
import type { Translator } from '../i18n/translate';
import { closeModal, openModal, wireModal } from './modal';
import { showToast, type Toast } from './toast';
import {
clearSession,
connectBunker,
@@ -310,14 +311,51 @@ function loggedInHtml(session: Session, npub: string, profile: Profile | null):
);
}
/** How long the login toast stays once it has nothing left to wait for. */
const TOAST_SETTLED_MS = 3000;
/**
* Say that the login landed, while the header cannot yet say it.
*
* The popup closes as soon as the signer answers, but the control it closes onto
* still reads "Log in" until a kind 0 comes back from the relays: a round trip that
* takes seconds on a cold cache and up to `PROFILE_TIMEOUT_MS` when a relay is dead.
* Those seconds looked like a login that quietly failed, which is the one reading
* that must never be available. So the toast confirms the half that is already true
* and names what the other half is waiting on.
*/
function announceLogin(): Toast {
const t = useI18n();
return showToast({
title: t('login.toast.loggedIn'),
body: t('login.toast.fetchingProfile'),
tone: 'busy',
duration: null, // Pinned: the profile lookup below is what ends it.
});
}
/** Close the login toast out, saying who the relays turned out to name. */
function settleLogin(toast: Toast, profile: Profile | null, t: Translator): void {
const name = profileName(profile);
toast.update({
// The name is the reader's own, out of their kind 0. `showToast` sets it as text.
body: name ? t('login.toast.profile', { name }) : t('login.toast.noProfile'),
tone: 'done',
duration: TOAST_SETTLED_MS,
});
}
/**
* Draw the header for the session as it stands.
*
* Runs twice on a restored session: once from the cache, immediately, so the avatar
* and name are there at first paint, and again if the relays turn up something newer.
* Neither pass changes the control's height.
*
* `announce` is set only by a fresh login, never by a restore or a redraw: a toast on
* every page arrival would be noise, and the wait it explains only exists once.
*/
async function renderHeader(): Promise<void> {
async function renderHeader(announce = false): Promise<void> {
const root = headerRoot();
if (!root) return;
@@ -328,10 +366,17 @@ async function renderHeader(): Promise<void> {
return;
}
const toast = announce ? announceLogin() : null;
const npub = await toNpub(session.pubkey);
const cached = await resolveProfile(session.pubkey);
if (readSession()?.pubkey !== session.pubkey) return; // Logged out while we waited.
if (readSession()?.pubkey !== session.pubkey) {
// Logged out while we waited. Nothing left to confirm, so take the toast with it.
toast?.dismiss();
return;
}
root.innerHTML = loggedInHtml(session, npub, cached);
if (toast) settleLogin(toast, cached, useI18n());
}
function setMenu(open: boolean): void {
@@ -397,8 +442,10 @@ export function initAuth(): void {
if (wired) return;
wired = true;
onSessionChange(() => {
void renderHeader();
onSessionChange((session) => {
// A session arriving here is always a fresh login: it is emitted on write, and a
// restored session is read, not written. A logout redraws without a toast.
void renderHeader(session !== null);
});
// The signer talks back through here: reconnecting, waiting, approve-in-a-window.