Build image / build-and-push (push) Successful in 13s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
import { Link, Outlet, useNavigate } from '@tanstack/react-router'
|
|
import { Toaster } from '@/components/ui/toast'
|
|
import { useLogout, useMe } from '@/lib/auth'
|
|
|
|
const navLinks = [
|
|
{ to: '/gardens', label: 'Gardens' },
|
|
{ to: '/plants', label: 'Plants' },
|
|
] as const
|
|
|
|
// TanStack Router concatenates the base className with activeProps/inactiveProps,
|
|
// so state-specific and conflicting utilities (text-muted vs text-fg) live in the
|
|
// state props — never in the base — to avoid ambiguous overrides.
|
|
const navLinkBase = 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors'
|
|
const navLinkActive = 'bg-border/60 text-fg'
|
|
const navLinkInactive = 'text-muted hover:bg-border/60 hover:text-fg'
|
|
|
|
/** Top-level chrome: a sticky nav bar plus the routed page in an <Outlet>. */
|
|
export function AppShell() {
|
|
const me = useMe()
|
|
const logout = useLogout()
|
|
const navigate = useNavigate()
|
|
const user = me.data
|
|
|
|
async function onLogout() {
|
|
try {
|
|
await logout.mutateAsync()
|
|
await navigate({ to: '/login' })
|
|
} catch {
|
|
// The logout request failed, so the session is still valid server-side:
|
|
// leave the user where they are (the button re-enables for a retry) rather
|
|
// than pretending they're signed out. logout.isError drives the title below.
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-full flex-col">
|
|
<header className="sticky top-0 z-10 border-b border-border bg-surface/90 backdrop-blur">
|
|
<nav className="mx-auto flex max-w-5xl items-center gap-4 px-4 py-3">
|
|
<Link to="/gardens" className="text-lg font-semibold text-accent-strong">
|
|
🌱 pansy
|
|
</Link>
|
|
|
|
<div className="flex flex-1 items-center gap-1">
|
|
{user &&
|
|
navLinks.map((l) => (
|
|
<Link
|
|
key={l.to}
|
|
to={l.to}
|
|
className={navLinkBase}
|
|
activeProps={{ className: navLinkActive }}
|
|
inactiveProps={{ className: navLinkInactive }}
|
|
>
|
|
{l.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{user ? (
|
|
<div className="flex items-center gap-2">
|
|
<span className="hidden text-sm text-muted sm:inline">{user.displayName}</span>
|
|
<button
|
|
type="button"
|
|
onClick={onLogout}
|
|
disabled={logout.isPending}
|
|
title={logout.isError ? 'Sign out failed — try again' : undefined}
|
|
className="rounded-md px-3 py-1.5 text-sm font-medium text-muted transition-colors hover:text-fg disabled:opacity-60"
|
|
>
|
|
{logout.isPending ? 'Signing out…' : logout.isError ? 'Retry sign out' : 'Sign out'}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<Link
|
|
to="/login"
|
|
className="rounded-md px-3 py-1.5 text-sm font-medium text-muted transition-colors hover:text-fg"
|
|
>
|
|
Sign in
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
</header>
|
|
|
|
<main className="mx-auto w-full max-w-5xl flex-1 px-4 py-6">
|
|
<Outlet />
|
|
</main>
|
|
|
|
<Toaster />
|
|
</div>
|
|
)
|
|
}
|