mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites). See CHANGELOG.md for the full per-section delta.
144 lines
5.3 KiB
TypeScript
144 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Bell,
|
|
ChevronDown,
|
|
LogIn,
|
|
LogOut,
|
|
Monitor,
|
|
Moon,
|
|
Settings,
|
|
Sun,
|
|
User,
|
|
} from "lucide-react";
|
|
import { Logo } from "@/components/ui/logo";
|
|
import Link from "next/link";
|
|
import { useAuth, useLogout } from "@/hooks/use-auth";
|
|
import { useTheme } from "@/components/providers";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
export function TopBar() {
|
|
const { user, isLoading } = useAuth();
|
|
const logout = useLogout();
|
|
const { theme, setTheme } = useTheme();
|
|
const { t } = useTranslations();
|
|
|
|
const themeIcon =
|
|
theme === "system" ? (
|
|
<Monitor className="h-4 w-4" />
|
|
) : theme === "dark" ? (
|
|
<Moon className="h-4 w-4" />
|
|
) : (
|
|
<Sun className="h-4 w-4" />
|
|
);
|
|
|
|
return (
|
|
<header className="bg-card/80 border-border sticky top-0 z-40 flex h-16 items-center justify-between border-b px-4 backdrop-blur-md md:px-6">
|
|
{/* Mobile logo */}
|
|
<Link href="/" className="flex items-center gap-2 md:hidden">
|
|
<Logo className="text-primary" size={20} />
|
|
<span className="font-bold tracking-tight">HealthLog</span>
|
|
</Link>
|
|
|
|
{/* Desktop: empty spacer (user controls are in sidebar) */}
|
|
<div className="hidden md:block" />
|
|
|
|
{/* Mobile-only auth section (desktop uses sidebar user section) */}
|
|
<div className="flex items-center gap-2 md:hidden">
|
|
{isLoading ? (
|
|
<div className="bg-muted h-4 w-20 animate-pulse rounded" />
|
|
) : user ? (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
aria-label={t("nav.userMenu")}
|
|
// v1.4.25 W8 — hit the WCAG 2.5.5 44 px touch-target floor on
|
|
// mobile. The text-only py-1.5 was previously ~30 px tall.
|
|
className="text-muted-foreground hover:text-foreground flex min-h-11 min-w-11 items-center gap-1.5 rounded-md px-2 py-1.5 text-sm transition-colors focus:outline-none"
|
|
>
|
|
<User className="h-4 w-4" />
|
|
<span className="hidden sm:inline">{user.username}</span>
|
|
<ChevronDown className="h-3 w-3 opacity-60" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-48">
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/settings/account" className="cursor-pointer">
|
|
<Settings className="mr-2 h-4 w-4" />
|
|
{t("nav.settings")}
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/notifications" className="cursor-pointer">
|
|
<Bell className="mr-2 h-4 w-4" />
|
|
{t("nav.notifications")}
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSub>
|
|
<DropdownMenuSubTrigger>
|
|
{themeIcon}
|
|
<span className="ml-2">{t("nav.theme")}</span>
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent>
|
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
<Monitor className="mr-2 h-4 w-4" />
|
|
{t("nav.themeSystem")}
|
|
{theme === "system" && (
|
|
<span className="text-primary ml-auto text-xs">
|
|
✓
|
|
</span>
|
|
)}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
<Moon className="mr-2 h-4 w-4" />
|
|
{t("nav.themeDark")}
|
|
{theme === "dark" && (
|
|
<span className="text-primary ml-auto text-xs">
|
|
✓
|
|
</span>
|
|
)}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
<Sun className="mr-2 h-4 w-4" />
|
|
{t("nav.themeLight")}
|
|
{theme === "light" && (
|
|
<span className="text-primary ml-auto text-xs">
|
|
✓
|
|
</span>
|
|
)}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
onClick={() => logout.mutate()}
|
|
className="text-destructive focus:text-destructive focus:bg-destructive/10 cursor-pointer"
|
|
>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
{t("nav.logout")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<Link
|
|
href="/auth/login"
|
|
// v1.4.25 W8 — match the WCAG 2.5.5 44 px touch-target floor.
|
|
className="text-muted-foreground hover:text-foreground flex min-h-11 min-w-11 items-center gap-2 rounded-md px-2 text-sm transition-colors"
|
|
>
|
|
<LogIn className="h-4 w-4" />
|
|
<span className="hidden sm:inline">{t("nav.login")}</span>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|