419ea11b14
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
User-facing Feedback screen (Settings -> Info, registered accounts only): a message (<=1024 runes) plus one optional attachment, an anti-spam gate (one unreviewed message at a time), and the operator's inline reply with a Settings/Info badge. Server-rendered admin console section (/_gm/feedback): unread/read/archived queue with per-user search, detail with read/reply/ archive/delete/delete-all, safe attachment serving (nosniff, images inline via <img>, others download-only). Introduces account_roles, the first per-account role table; feedback_banned blocks only feedback submission, granted/revoked from /users and the delete-with-block action. - migration 00004_feedback (feedback_messages + account_roles) + jetgen - backend internal/feedback (store+service), internal/account/roles.go - wire: FlatBuffers feedback.submit/get/unread; gateway guest gate (Op.NonGuest, is_guest via session resolve) -> guest_forbidden before any backend call - reply push reuses NotificationEvent with a new admin_reply sub-kind - UI: /feedback route + screen, attachment picker, badge, channel detection, i18n - tests: feedback unit (Go+UI), gateway guest-gate, inttest lifecycle, e2e - docs: PLAN stage 19, ARCHITECTURE s15, FUNCTIONAL(+ru), TESTING, READMEs
65 lines
2.2 KiB
TypeScript
65 lines
2.2 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'
|
|
| 'feedback'
|
|
| 'stats'
|
|
| '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 'feedback':
|
|
return { name: 'feedback', params: {} };
|
|
case 'stats':
|
|
return { name: 'stats', params: {} };
|
|
default:
|
|
return { name: 'notfound', params: {} };
|
|
}
|
|
}
|