import Link from "next/link"; import { eq } from "drizzle-orm"; import { cn } from "@/lib/utils"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; import { getRegistry } from "@/modules/_core/registry"; import { householdMembers, households, users } from "@/modules/_core/schema"; import { BrandMark } from "@/components/brand-mark"; import { NavIcon } from "@/components/nav-icon"; import { NavLink } from "@/components/nav-link"; import { AvatarFallbackWithName } from "@/components/avatar-fallback"; type Variant = "side" | "top"; const PRIMARY_NAV: Array<{ href: string; label: string; icon: string }> = [ { href: "/", label: "Dashboard", icon: "home" }, ]; const SECONDARY_NAV: Array<{ href: string; label: string; icon: string }> = [ { href: "/settings#sharing", label: "Share links", icon: "link" }, ]; async function getHouseholdInfo(userId: string) { const [row] = await db .select({ household: households }) .from(householdMembers) .innerJoin(households, eq(householdMembers.householdId, households.id)) .where(eq(householdMembers.userId, userId)) .limit(1); if (!row) return null; const members = await db .select({ id: users.id, name: users.name, image: users.image, email: users.email }) .from(householdMembers) .innerJoin(users, eq(householdMembers.userId, users.id)) .where(eq(householdMembers.householdId, row.household.id)) .limit(6); return { household: row.household, members }; } export async function Sidebar({ variant = "side" }: { variant?: Variant }) { const { modules } = getRegistry(); const moduleNav = modules.flatMap((m) => (m.nav ? [m.nav] : [])); const navItems = [ ...PRIMARY_NAV, ...moduleNav.map((n) => ({ href: n.href, label: n.label, icon: n.icon ?? "circle" })), { href: "/settings", label: "Settings", icon: "settings" }, ]; const session = await auth(); const householdInfo = session?.user?.id ? await getHouseholdInfo(session.user.id) : null; if (variant === "top") { return (
famapp {navItems.map((n) => ( ))}
{householdInfo && ( {householdInfo.household.name} )}
); } return ( ); } function HouseholdPill({ info, }: { info: NonNullable>>; }) { const visible = info.members.slice(0, 3); return (
{visible.map((m, i) => ( 0 && "-ml-1.5", "shadow-[0_0_0_1.5px_var(--card)]")} fallbackStyle={{ background: avatarColor(m.id) }} title={m.name ?? m.email ?? undefined} /> ))}
{info.household.name}
{info.members.length} {info.members.length === 1 ? "member" : "members"}
); } // Stable color from id — uses the 5 functional accents in rotation. const ACCENT_HUES = [ "var(--c-household)", "var(--c-private)", "var(--c-kids)", "var(--c-work)", "var(--c-bills)", ]; function avatarColor(id: string): string { let hash = 0; for (let i = 0; i < id.length; i++) hash = (hash * 31 + id.charCodeAt(i)) & 0xffff; return ACCENT_HUES[hash % ACCENT_HUES.length] ?? "var(--c-household)"; }