Files
vyndr/web/src/app/page.tsx
T
builtbykev 907c7b17c1 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>
2026-06-15 23:27:58 -04:00

60 lines
2.1 KiB
TypeScript

'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/contexts/AuthContext';
import Hero from '@/components/Hero';
// Session 17 — game-count strip mounted between the hero and the
// existing LivePropsStrip. Shows "X NBA · Y WNBA · Z MLB games
// being graded right now" with a signup CTA. Hides itself when
// every sport returns zero (off-hours / upstream outages).
import TonightsSlate from '@/components/TonightsSlate';
import LivePropsStrip from '@/components/LivePropsStrip';
// Session 23 — all-day intelligence teasers. Free/cheap content that
// keeps the landing page alive even when odds-api props are empty.
// Both self-hide when there's nothing to show.
import StreaksPanel from '@/components/StreaksPanel';
import HotListPanel from '@/components/HotListPanel';
import Features from '@/components/Features';
import HowItWorks from '@/components/HowItWorks';
import Pricing from '@/components/Pricing';
import FAQ from '@/components/FAQ';
// Footer is mounted globally in the root layout (Session 34) — no per-page import.
export default function Home() {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && user) router.replace('/dashboard');
}, [user, loading, router]);
// While we know the user is signed in we suppress the marketing
// render to avoid a flicker before the redirect lands.
if (loading || user) {
return (
<section style={{ minHeight: '80vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<p className="mono" style={{ color: 'var(--text-tertiary)', fontSize: 13, letterSpacing: '0.08em', textTransform: 'uppercase' }}>
Loading the slate
</p>
</section>
);
}
return (
<>
<Hero />
<TonightsSlate />
<LivePropsStrip />
<div style={{ maxWidth: 960, margin: '0 auto', padding: '0 16px' }}>
<StreaksPanel sport="nba" tier="free" limit={3} />
<HotListPanel sport="mlb" tier="free" limit={3} />
</div>
<Features />
<HowItWorks />
<Pricing />
<FAQ />
</>
);
}