From 411137b170acceb803d4325873daa7df586e9f60 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 22:47:22 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/app-shell.tsx | 160 ++++++++++++++++++++++++++++ src/components/protected-layout.tsx | 29 +++++ src/routes/__root.tsx | 34 +++--- 3 files changed, 204 insertions(+), 19 deletions(-) create mode 100644 src/components/app-shell.tsx create mode 100644 src/components/protected-layout.tsx diff --git a/src/components/app-shell.tsx b/src/components/app-shell.tsx new file mode 100644 index 0000000..d119f0c --- /dev/null +++ b/src/components/app-shell.tsx @@ -0,0 +1,160 @@ +import { Link, useLocation, useNavigate } from "@tanstack/react-router"; +import { useAuth } from "@/lib/auth"; +import { Button } from "@/components/ui/button"; +import { + Briefcase, + Users, + FileText, + Receipt, + ShieldCheck, + LogOut, + Scale, + LayoutDashboard, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { ReactNode } from "react"; + +interface NavItem { + to: string; + label: string; + icon: typeof Briefcase; + adminOnly?: boolean; +} + +const NAV: NavItem[] = [ + { to: "/", label: "Dashboard", icon: LayoutDashboard }, + { to: "/clients", label: "Clients", icon: Users }, + { to: "/cases", label: "Cases", icon: Briefcase }, + { to: "/invoices", label: "Invoices", icon: Receipt }, + { to: "/admin/users", label: "Users", icon: ShieldCheck, adminOnly: true }, +]; + +export function AppShell({ children }: { children: ReactNode }) { + const { user, signOut, isAdmin, roles } = useAuth(); + const location = useLocation(); + const navigate = useNavigate(); + + const handleSignOut = async () => { + await signOut(); + navigate({ to: "/login" }); + }; + + return ( +
+ {/* Sidebar */} + + + {/* Mobile top bar */} +
+ + + Counsel + + +
+ +
+
+ {NAV.filter((n) => !n.adminOnly || isAdmin).map((item) => { + const active = + item.to === "/" + ? location.pathname === "/" + : location.pathname.startsWith(item.to); + return ( + + {item.label} + + ); + })} +
+ {children} +
+
+ ); +} + +export function PageHeader({ + title, + description, + actions, +}: { + title: string; + description?: string; + actions?: ReactNode; +}) { + return ( +
+
+

{title}

+ {description &&

{description}

} +
+ {actions &&
{actions}
} +
+ ); +} + +export function PageContainer({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/src/components/protected-layout.tsx b/src/components/protected-layout.tsx new file mode 100644 index 0000000..96b2bc6 --- /dev/null +++ b/src/components/protected-layout.tsx @@ -0,0 +1,29 @@ +import { useEffect, type ReactNode } from "react"; +import { useNavigate } from "@tanstack/react-router"; +import { useAuth } from "@/lib/auth"; +import { AppShell } from "@/components/app-shell"; +import { Loader2 } from "lucide-react"; + +export function ProtectedLayout({ children, adminOnly }: { children: ReactNode; adminOnly?: boolean }) { + const { loading, session, isAdmin } = useAuth(); + const navigate = useNavigate(); + + useEffect(() => { + if (loading) return; + if (!session) { + navigate({ to: "/login" }); + } else if (adminOnly && !isAdmin) { + navigate({ to: "/" }); + } + }, [loading, session, isAdmin, adminOnly, navigate]); + + if (loading || !session || (adminOnly && !isAdmin)) { + return ( +
+ +
+ ); + } + + return {children}; +} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index a23245a..ed8366d 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,15 +1,16 @@ -import { Outlet, Link, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"; - +import { Outlet, createRootRoute, HeadContent, Scripts, Link } from "@tanstack/react-router"; +import { Toaster } from "@/components/ui/sonner"; +import { AuthProvider } from "@/lib/auth"; import appCss from "../styles.css?url"; function NotFoundComponent() { return (
-

404

+

404

Page not found

- The page you're looking for doesn't exist or has been moved. + The page you're looking for doesn't exist.

; + return ( + + + + + ); }