a679d9cdcb
PR-feedback round on #60: - Time-zone field is now a continent-grouped <select> populated from `Intl.supportedValuesOf("timeZone")`, with the browser-detected zone pre-selected when no value is stored. A stored zone the runtime no longer advertises is preserved as an "Other" entry. - Saving the profile no longer kicks the user back to the lobby: the form stays put and shows a transient `saved` notice, cleared on the next edit. Only `cancel` returns to the lobby. - New `lib/account-store.svelte.ts` caches `user.account.get` for the session; lobby + profile share it through `account.ensure()`, so navigating Overview ⇄ Profile no longer flashes the "loading account…" placeholder or fires a second gateway call. Profile save writes through to the store so the shell identity strip picks up the new display name without refetching. Cleared on logout to prevent identity bleed between accounts. - e2e: existing 4 cases adjusted for save-stay; added two new ones for the timezone dropdown and identity-strip stability across navigation. - Docs: `ui/docs/lobby.md` updated to describe the shared cache, the new timezone picker shape, and the save-stay behaviour.
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
// `AccountStore` is the session-wide cache for the caller's
|
|
// `user.account.get` aggregate. The lobby shell and every post-login
|
|
// screen read the identity (display name, immutable user_name, time
|
|
// zone, …) from the same rune, so navigating between Overview and
|
|
// Profile does not refetch and does not flash the
|
|
// `lobby.account_loading` placeholder.
|
|
//
|
|
// `ensure(client)` fetches once on first call, dedupes concurrent
|
|
// callers onto a single in-flight promise, and resolves immediately
|
|
// from the cache thereafter. `set(account)` is the write-through
|
|
// path used by Profile after `user.profile.update` /
|
|
// `user.settings.update` succeeds — both the shell and the screen
|
|
// pick up the change without an extra round-trip. `clear()` resets
|
|
// the cache on logout so a different user signing in on the same
|
|
// browser does not briefly see the previous identity.
|
|
//
|
|
// The store is intentionally narrow: it caches one struct, never
|
|
// retries on failure (the caller decides), and exposes no error
|
|
// state of its own. Callers that need a tighter error surface (the
|
|
// Profile form) catch the rejection from `ensure(client)` directly.
|
|
|
|
import type { GalaxyClient } from "../api/galaxy-client";
|
|
import { getMyAccount, type Account } from "../api/account";
|
|
|
|
class AccountStore {
|
|
current: Account | null = $state(null);
|
|
#inFlight: Promise<Account> | null = null;
|
|
|
|
/**
|
|
* ensure returns the cached `Account` when present, otherwise issues
|
|
* `user.account.get` through the supplied client and caches the
|
|
* result. Concurrent callers during the first fetch share the same
|
|
* in-flight promise so the gateway only sees one request per
|
|
* session.
|
|
*/
|
|
ensure(client: GalaxyClient): Promise<Account> {
|
|
if (this.current !== null) {
|
|
return Promise.resolve(this.current);
|
|
}
|
|
if (this.#inFlight !== null) {
|
|
return this.#inFlight;
|
|
}
|
|
const pending = getMyAccount(client)
|
|
.then((account) => {
|
|
this.current = account;
|
|
return account;
|
|
})
|
|
.finally(() => {
|
|
this.#inFlight = null;
|
|
});
|
|
this.#inFlight = pending;
|
|
return pending;
|
|
}
|
|
|
|
/**
|
|
* set replaces the cached `Account` with the supplied value. Used
|
|
* by the Profile screen after a successful save so both the form
|
|
* and the shell identity strip pick up the new fields without a
|
|
* second round-trip.
|
|
*/
|
|
set(next: Account): void {
|
|
this.current = next;
|
|
}
|
|
|
|
/**
|
|
* clear resets the cache. Called on logout so a different user
|
|
* signing in on the same browser does not briefly see the
|
|
* previous identity through the rune.
|
|
*/
|
|
clear(): void {
|
|
this.current = null;
|
|
this.#inFlight = null;
|
|
}
|
|
}
|
|
|
|
export const account = new AccountStore();
|