Files
GMW/services/frontend/src/app/dashboard/layout.tsx
T

110 lines
3.1 KiB
TypeScript
Raw Normal View History

"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { Suspense, useEffect, useRef } from "react";
import { uiStateApi } from "@/lib/api";
import { Header } from "@/components/layout/header";
import { MobileTabBar } from "@/components/layout/mobile-tab-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { MascotChatbot } from "@/features/mascot/mascot-chatbot";
import { AuthProvider, useAuth } from "@/lib/hooks/use-auth";
import { WsProvider } from "@/lib/ws/context";
function DashboardGuard({ children }: { children: React.ReactNode }) {
const { authenticated, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !authenticated) {
router.push("/");
}
}, [authenticated, loading, router]);
if (loading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
);
}
if (!authenticated) return null;
return <>{children}</>;
}
function DashboardShell({ children }: { children: React.ReactNode }) {
const searchParams = useSearchParams();
const router = useRouter();
const restored = useRef(false);
const activeTab = (searchParams.get("tab") ?? "messages") as
| "messages"
| "live"
| "dashboard";
// Restore persisted tab on mount (only if no explicit tab in URL)
useEffect(() => {
if (restored.current) return;
const tabParam = searchParams.get("tab");
if (tabParam) {
restored.current = true;
return; // explicit tab in URL — don't override
}
uiStateApi
.get()
.then((state) => {
restored.current = true;
const savedTab = state.active_tab;
if (savedTab && savedTab !== activeTab) {
router.replace(`/dashboard?tab=${savedTab}`);
}
})
.catch(() => {
restored.current = true;
});
}, [searchParams, activeTab, router]);
// Persist tab changes
useEffect(() => {
if (!restored.current) return;
uiStateApi.save({ active_tab: activeTab }).catch(() => {});
}, [activeTab]);
return (
<div className="min-h-screen bg-background">
<Sidebar activeTab={activeTab} />
<div className="md:pl-56 flex flex-col min-h-screen">
<Header />
<main className="flex-1 p-4 md:p-6 pb-20 md:pb-6">{children}</main>
</div>
<MobileTabBar activeTab={activeTab} />
</div>
);
}
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<AuthProvider>
<DashboardGuard>
<WsProvider>
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center">
<div className="size-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
}
>
<DashboardShell>{children}</DashboardShell>
</Suspense>
<MascotChatbot />
</WsProvider>
</DashboardGuard>
</AuthProvider>
);
}