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,33 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { isGatedRoute } from '@/lib/routes';
|
||||
|
||||
/**
|
||||
* Client-side auth gate (§12). Our session lives in the Supabase client
|
||||
* (localStorage), not an httpOnly cookie a server middleware could read — so
|
||||
* the gate runs here, in the browser, on top of the existing Supabase auth.
|
||||
*
|
||||
* Gated routes (a user's own ledger / tracker / account / alerts — see
|
||||
* lib/routes.js) bounce signed-out visitors to /login, remembering where they
|
||||
* were headed via the `?next=` param the login page already consumes. We wait
|
||||
* for auth to finish loading before deciding, so a logged-in user is never
|
||||
* flashed to /login on a hard refresh.
|
||||
*/
|
||||
export default function AuthGate({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const pathname = usePathname() || '';
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
if (!user && isGatedRoute(pathname)) {
|
||||
const next = encodeURIComponent(pathname);
|
||||
router.replace(`/login?next=${next}`);
|
||||
}
|
||||
}, [user, loading, pathname, router]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user