import DaemonStatus from "@/components/DaemonStatus";
import LanguageSwitch from "@/components/LanguageSwitch";
import { Button } from "@/components/ui/button";
import { useAuth } from "@/contexts/AuthContext";
import { useI18n } from "@/i18n/I18nProvider";
import { listProjects } from "@/lib/queries";
import type { ProjectSummary } from "@/types";
import { Activity, Inbox, LogOut, ScrollText } from "lucide-react";
import { useEffect, useState } from "react";
import { NavLink, Outlet, useNavigate } from "react-router-dom";
export default function DashboardLayout() {
const { session, signOut } = useAuth();
const { t } = useI18n();
const navigate = useNavigate();
const [projects, setProjects] = useState<ProjectSummary[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let alive = true;
listProjects()
.then((list) => {
if (alive) {
setProjects(list);
setLoading(false);
}
})
.catch((e) => {
if (alive) {
setError(String(e?.message ?? e));
setLoading(false);
}
});
return () => {
alive = false;
};
}, []);
async function handleSignOut() {
await signOut();
navigate("/", { replace: true });
}
return (
<div className="min-h-screen bg-background text-foreground flex">
<aside className="w-64 border-r flex flex-col">
<div className="p-4 border-b space-y-2">
<div className="space-y-0.5">
<div className="text-lg font-black tracking-[0.2em] leading-none">
DEVIST
</div>
<div className="text-[10px] uppercase tracking-[0.3em] text-muted-foreground">
Dashboard
</div>
<div className="pt-1.5 text-xs text-muted-foreground truncate">
{session?.user.email}
</div>
</div>
<DaemonStatus />
<div className="pt-1">
<LanguageSwitch />
</div>
</div>
<nav className="p-2 space-y-1 text-sm">
<NavItem to="/dashboard" icon={<Activity size={14} />}>
{t("nav.overview")}
</NavItem>
<NavItem to="/dashboard/inbox" icon={<Inbox size={14} />}>
{t("nav.inbox")}
</NavItem>
<NavItem to="/dashboard/rules" icon={<ScrollText size={14} />}>
{t("nav.rules")}
</NavItem>
</nav>
<div className="px-4 pt-4 pb-1 text-[11px] uppercase tracking-wide text-muted-foreground">
{t("nav.projects")}
</div>
<div className="flex-1 overflow-y-auto px-2 pb-2 space-y-0.5 text-sm">
{loading && (
<div className="text-xs text-muted-foreground px-3 py-2">{t("common.loading")}</div>
)}
{error && <div className="text-xs text-red-600 px-3 py-2">{error}</div>}
{!loading && !error && projects.length === 0 && (
<div className="text-xs text-muted-foreground px-3 py-2">{t("nav.empty")}</div>
)}
{projects.map((p) => (
<NavLink
key={p.project}
to={`/dashboard/projects/${encodeURIComponent(p.project)}`}
className={({ isActive }) =>
`flex items-center justify-between px-3 py-1.5 rounded-md hover:bg-muted ${
isActive ? "bg-muted font-medium" : ""
}`
}
>
<span className="truncate">{p.project}</span>
<span className="text-xs text-muted-foreground">{p.event_count}</span>
</NavLink>
))}
</div>
<div className="border-t p-2">
<Button
variant="ghost"
size="sm"
className="w-full justify-start gap-2"
onClick={handleSignOut}
>
<LogOut size={14} />
{t("common.signOut")}
</Button>
</div>
</aside>
<main className="flex-1 overflow-y-auto">
<Outlet />
</main>
</div>
);
}
function NavItem({
to,
icon,
children,
}: {
to: string;
icon: React.ReactNode;
children: React.ReactNode;
}) {
return (
<NavLink
to={to}
end
className={({ isActive }) =>
`flex items-center gap-2 px-3 py-1.5 rounded-md hover:bg-muted ${
isActive ? "bg-muted font-medium" : ""
}`
}
>
{icon}
{children}
</NavLink>
);
}