Files
scrabble-game/ui/src/lib/routeparse.ts
T
Ilia Denisov 4aa6174968
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
feat(payments): wallet screen with balances, benefits and storefront
Add the "Кошелёк" section to the settings hub: context-visible chip
balances, active benefits (no-ads term/forever, hints) and a storefront of
chip-priced values and money-priced chip packs. Guests have no wallet; the
Google Play build hides the money purchases behind a RuStore stub; a web
purchase that would draw VK/Telegram chips warns first.

Add the catalog read path the storefront needs — a context-projected
GET /api/v1/user/wallet/catalog (payments service + store, gateway op
wallet.catalog, FBS Catalog/CatalogProduct/CatalogAtom, client decode) —
plus the client leg for the existing wallet.get/buy ops. Value spends reuse
the existing spend path; the chip-pack purchase (money order flow) arrives
with payment intake, so its action is a disabled placeholder for now.

Covered by Go unit (catalog projection) + integration (/wallet/catalog over
Postgres), vitest (formatting, spendable selection, web-spend warning, GP
flag, codec + gateway encode round-trips) and Playwright mock e2e (render,
guest-hidden, GP stub, warning) on Chromium + WebKit.
2026-07-08 12:37:32 +02:00

74 lines
2.5 KiB
TypeScript

// Pure hash-path -> Route parsing for the router. Kept dependency-free and free of any
// reactive state so it unit-tests in the node environment; router.svelte.ts wraps it with
// the reactive route rune and navigation.
export type RouteName =
| 'login'
| 'lobby'
| 'new'
| 'game'
| 'gameChat'
| 'gameCheck'
| 'profile'
| 'settings'
| 'about'
| 'friends'
| 'wallet'
| 'feedback'
| 'stats'
| 'confirm'
| 'notfound';
export interface Route {
name: RouteName;
params: Record<string, string>;
}
/**
* parse maps a location hash to a Route. An empty hash is the lobby root. A Telegram Mini
* App cold launch appends its launch params to the URL fragment (#tgWebAppData=...&
* tgWebAppVersion=...); those are launch metadata, not a route, so the fragment is treated
* as the lobby root. Otherwise it would parse as notfound, and bootstrap's navigate('/')
* would then re-key the route pane (notfound -> lobby), sliding the lobby in on launch as if
* returning from another screen.
*/
export function parse(hash: string): Route {
const raw = hash.replace(/^#/, '');
if (raw === '' || raw.startsWith('tgWebApp')) return { name: 'lobby', params: {} };
const path = raw.split('?')[0];
const seg = path.split('/').filter(Boolean);
if (seg.length === 0) return { name: 'lobby', params: {} };
switch (seg[0]) {
case 'login':
return { name: 'login', params: {} };
case 'new':
return { name: 'new', params: {} };
case 'game':
if (!seg[1]) return { name: 'notfound', params: {} };
if (seg[2] === 'chat') return { name: 'gameChat', params: { id: seg[1] } };
if (seg[2] === 'check') return { name: 'gameCheck', params: { id: seg[1] } };
return { name: 'game', params: { id: seg[1] } };
case 'profile':
return { name: 'profile', params: {} };
case 'settings':
return { name: 'settings', params: {} };
case 'about':
return { name: 'about', params: {} };
case 'friends':
return { name: 'friends', params: {} };
case 'wallet':
return { name: 'wallet', params: {} };
case 'feedback':
return { name: 'feedback', params: {} };
case 'stats':
return { name: 'stats', params: {} };
case 'confirm':
// The one-tap email deeplink: /confirm/<token>. The token is the confirmation
// authorization; the screen POSTs it (prefetch-safe), so a mail scanner's GET
// does not consume it.
return { name: 'confirm', params: { token: seg[1] ?? '' } };
default:
return { name: 'notfound', params: {} };
}
}