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 (
+
+
+
+
+ );
}