refactor(ts): convert the app shell to TSX (B7)
App.tsx (routing + QueryClient), main.tsx (entry; index.html updated to
main.tsx), useAuth.tsx (typed AuthContextValue/User + MeResponse; uses the safe
useContext()||defaults pattern so no `never` trap), and the layout components
(Layout, Sidebar, NavPill, BrandBlock — NavItem type, LucideIcon-typed nav
config, casts for the untyped overdue/simplefin API responses). TS caught a dead
prop: App passed mainContentId to TrackerPage, which takes no params — removed.
typecheck 0, lint 0 errors (39 warns), build green, 17/17 e2e probe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:49:57 -05:00
|
|
|
import { useState, useEffect } from 'react';
|
2026-06-07 15:14:09 -05:00
|
|
|
import { Link, Outlet, useLocation } from 'react-router-dom';
|
2026-05-03 19:51:57 -05:00
|
|
|
import AppNavigation from './Sidebar';
|
2026-05-28 22:06:15 -05:00
|
|
|
import { api } from '@/api';
|
2026-07-06 12:28:25 -05:00
|
|
|
import { APP_VERSION } from '@/lib/version';
|
|
|
|
|
import { BRAND } from '@/lib/brand';
|
2026-06-07 15:14:09 -05:00
|
|
|
import PageTransition from '@/components/PageTransition';
|
2026-07-05 15:54:19 -05:00
|
|
|
import { FormatSync } from '@/components/FormatSync';
|
2026-05-28 22:06:15 -05:00
|
|
|
|
|
|
|
|
function SimplefinBadge() {
|
|
|
|
|
const [enabled, setEnabled] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-07-06 14:23:53 -05:00
|
|
|
api
|
|
|
|
|
.simplefinStatus()
|
|
|
|
|
.then((d) => setEnabled(!!(d as { enabled?: boolean }).enabled))
|
|
|
|
|
.catch((err) => console.error('[Layout] simplefinStatus check failed', err));
|
2026-05-28 22:06:15 -05:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
if (!enabled) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span className="inline-flex items-center gap-1.5">
|
|
|
|
|
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" aria-hidden="true" />
|
|
|
|
|
SimpleFIN
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-05-03 19:51:57 -05:00
|
|
|
|
refactor(ts): convert the app shell to TSX (B7)
App.tsx (routing + QueryClient), main.tsx (entry; index.html updated to
main.tsx), useAuth.tsx (typed AuthContextValue/User + MeResponse; uses the safe
useContext()||defaults pattern so no `never` trap), and the layout components
(Layout, Sidebar, NavPill, BrandBlock — NavItem type, LucideIcon-typed nav
config, casts for the untyped overdue/simplefin API responses). TS caught a dead
prop: App passed mainContentId to TrackerPage, which takes no params — removed.
typecheck 0, lint 0 errors (39 warns), build green, 17/17 e2e probe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 19:49:57 -05:00
|
|
|
export default function Layout({ mainContentId }: { mainContentId?: string }) {
|
2026-06-07 15:14:09 -05:00
|
|
|
const location = useLocation();
|
|
|
|
|
|
2026-05-03 19:51:57 -05:00
|
|
|
return (
|
2026-07-06 14:23:53 -05:00
|
|
|
<div
|
|
|
|
|
className="min-h-screen bg-[radial-gradient(circle_at_top_left,oklch(var(--primary)/0.08),transparent_28rem),radial-gradient(circle_at_top_right,oklch(var(--accent)/0.08),transparent_24rem),linear-gradient(180deg,oklch(var(--background)),oklch(var(--muted)/0.16))] text-foreground"
|
2026-05-10 00:18:36 -05:00
|
|
|
role="main"
|
|
|
|
|
aria-labelledby={mainContentId}
|
|
|
|
|
>
|
|
|
|
|
<AppNavigation mainContentId={mainContentId} />
|
2026-05-03 19:51:57 -05:00
|
|
|
|
|
|
|
|
<main className="w-full">
|
2026-07-06 14:23:53 -05:00
|
|
|
<div
|
|
|
|
|
className="mx-auto w-full max-w-[1500px] px-4 py-6 sm:px-6 lg:px-8 lg:py-8"
|
2026-05-10 00:18:36 -05:00
|
|
|
id={mainContentId}
|
|
|
|
|
>
|
2026-06-07 15:14:09 -05:00
|
|
|
<PageTransition routeKey={location.pathname}>
|
2026-07-05 15:54:19 -05:00
|
|
|
<FormatSync>
|
|
|
|
|
<Outlet />
|
|
|
|
|
</FormatSync>
|
2026-06-07 15:14:09 -05:00
|
|
|
</PageTransition>
|
2026-05-03 19:51:57 -05:00
|
|
|
</div>
|
|
|
|
|
</main>
|
2026-07-06 14:23:53 -05:00
|
|
|
<footer
|
|
|
|
|
className="mx-auto flex w-full max-w-[1500px] flex-wrap items-center justify-between gap-x-4 gap-y-3 px-4 pb-6 text-xs text-muted-foreground sm:px-6 lg:px-8"
|
2026-05-10 00:18:36 -05:00
|
|
|
aria-label="Footer"
|
|
|
|
|
>
|
2026-07-06 12:28:25 -05:00
|
|
|
<span className="inline-flex items-center gap-2">
|
|
|
|
|
<span className="h-1.5 w-1.5 rounded-full bg-primary" aria-hidden="true" />
|
2026-07-06 14:23:53 -05:00
|
|
|
<span>
|
|
|
|
|
{BRAND.name} v{APP_VERSION}
|
|
|
|
|
</span>
|
2026-07-06 12:28:25 -05:00
|
|
|
</span>
|
|
|
|
|
<span className="flex flex-wrap items-center gap-x-4 gap-y-2">
|
2026-07-06 14:23:53 -05:00
|
|
|
<Link to="/about" className="underline-offset-4 hover:text-foreground hover:underline">
|
|
|
|
|
About
|
|
|
|
|
</Link>
|
|
|
|
|
<Link
|
|
|
|
|
to="/release-notes"
|
|
|
|
|
className="underline-offset-4 hover:text-foreground hover:underline"
|
|
|
|
|
>
|
|
|
|
|
Release Notes
|
|
|
|
|
</Link>
|
2026-07-06 12:28:25 -05:00
|
|
|
<SimplefinBadge />
|
|
|
|
|
</span>
|
2026-05-04 20:12:57 -05:00
|
|
|
</footer>
|
2026-05-03 19:51:57 -05:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|