Session 34: Design system Phase C — app shell, nav, routing, auth gate, footer, 404 (1818 tests)
VYNDR 2.0 conversion, Phase C (the frame every page sits inside). Frontend-only; zero backend changes. - Nav rewritten: new .wm Wordmark, mono uppercase links, More dropdown, search/ bell/read-meter/avatar, Ticker under the bar. layout main paddingTop 64 -> 96. - Routing: web/src/lib/routes.js (GATED/OPEN/HASH_ALIASES, isGatedRoute, resolveHashAlias). Client AuthGate bounces signed-out users off personal routes to /login?next=. HashRedirect maps #scan/#terminal to real routes. - Footer rewritten to system voice + Detroit signature; mounted globally in layout (removed per-page dup). - 404 converted to the north star (scanlines, crt-sweep, glitch wordmark, amber). - Stub pages for terminal/compare/invite/help/about/notifications via RouteStub. Honest reconciliations: auth gate is client-side (no auth-helpers pkg; session is client-side Supabase); GATED narrowed to protect the free-scan funnel; did not stub over existing real pages; redirect param is ?next= (what /login reads). 26 new tests. Backend 1792 -> 1818, 142 suites, zero regressions. Web build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/* ============================================================
|
||||
VYNDR 2.0 — app-shell routing config (§6, §12).
|
||||
Plain CommonJS so the client AuthGate/HashRedirect import it
|
||||
(allowJs) AND the Jest suite requires it directly (no transform).
|
||||
|
||||
AUTH NOTE: our auth is client-side Supabase (session in
|
||||
localStorage via @supabase/supabase-js), and the existing
|
||||
middleware.ts is locale-only. A Next server middleware can't read
|
||||
that session, so the gate is enforced CLIENT-side by <AuthGate>.
|
||||
This file is the single source of truth for which routes gate.
|
||||
============================================================ */
|
||||
|
||||
/* Gated — require an authenticated user. Deliberately narrower than the
|
||||
prototype's GATED set: the prototype also gated dashboard + scan, but those
|
||||
are OUR free-scan acquisition funnel (anon/free users get 5 reads), so
|
||||
gating them would be a monetization regression. We gate only the genuinely
|
||||
personal surfaces (a user's own ledger, bets, account, alerts). */
|
||||
const GATED_ROUTES = [
|
||||
'/ledger',
|
||||
'/tracker',
|
||||
'/account',
|
||||
'/profile',
|
||||
'/settings',
|
||||
'/notifications',
|
||||
'/invite',
|
||||
];
|
||||
|
||||
/* Open — reachable without auth (marketing + the free funnel + auth itself). */
|
||||
const OPEN_ROUTES = [
|
||||
'/',
|
||||
'/dashboard',
|
||||
'/slate',
|
||||
'/scan',
|
||||
'/terminal',
|
||||
'/compare',
|
||||
'/game',
|
||||
'/pricing',
|
||||
'/blog',
|
||||
'/article',
|
||||
'/responsible-gambling',
|
||||
'/help',
|
||||
'/about',
|
||||
'/terms',
|
||||
'/privacy',
|
||||
'/login',
|
||||
'/signup',
|
||||
'/auth',
|
||||
'/forgot-password',
|
||||
'/verify',
|
||||
'/welcome',
|
||||
'/offline',
|
||||
'/upgrade',
|
||||
];
|
||||
|
||||
/* Hash deep-link aliases (§C.3.4). The prototype was a single HTML file using
|
||||
#scan / #terminal; we keep Next file-based routing and treat these hashes as
|
||||
redirects so old share links and PWA shortcuts still resolve. */
|
||||
const HASH_ALIASES = {
|
||||
'#slate': '/dashboard',
|
||||
'#dashboard': '/dashboard',
|
||||
'#scan': '/scan',
|
||||
'#terminal': '/terminal',
|
||||
'#compare': '/compare',
|
||||
'#ledger': '/ledger',
|
||||
'#tracker': '/tracker',
|
||||
'#account': '/account',
|
||||
'#pricing': '/pricing',
|
||||
'#blog': '/blog',
|
||||
'#invite': '/invite',
|
||||
'#notifications': '/notifications',
|
||||
};
|
||||
|
||||
/** True when `pathname` falls under a gated route (exact or nested). */
|
||||
function isGatedRoute(pathname) {
|
||||
if (!pathname) return false;
|
||||
return GATED_ROUTES.some((r) => pathname === r || pathname.startsWith(r + '/'));
|
||||
}
|
||||
|
||||
/** Resolve a window.location.hash (e.g. "#scan") to a real route, or null. */
|
||||
function resolveHashAlias(hash) {
|
||||
if (!hash) return null;
|
||||
return HASH_ALIASES[hash] || null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
GATED_ROUTES,
|
||||
OPEN_ROUTES,
|
||||
HASH_ALIASES,
|
||||
isGatedRoute,
|
||||
resolveHashAlias,
|
||||
};
|
||||
Reference in New Issue
Block a user