From b12d83c4f279f1f00ea0fc3692973e1db72158f1 Mon Sep 17 00:00:00 2001 From: Groom Book CTO Date: Thu, 19 Mar 2026 02:12:43 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20flip=20routing=20=E2=80=94=20customer?= =?UTF-8?q?=20portal=20at=20/,=20admin=20at=20/admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all admin dashboard routes under /admin prefix and mount the customer portal at root (/). This gives customers clean, shareable URLs while staff bookmark /admin. - Admin routes: /admin, /admin/clients, /admin/services, etc. - Customer portal: / (root) - Admin nav "Customer Portal" link points to / for staff preview - Updated tests for new route structure and fixed React 19 act compat Closes #56 Co-Authored-By: Paperclip --- apps/web/src/App.tsx | 32 +++++++++++++--------- apps/web/src/__tests__/App.test.tsx | 42 +++++++++++++++++------------ 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index f61a751..b3ef7fc 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -10,14 +10,14 @@ import { GroupBookingPage } from "./pages/GroupBooking.js"; import { CustomerPortal } from "./portal/CustomerPortal.js"; const NAV_LINKS = [ - { to: "/", label: "Appointments" }, - { to: "/clients", label: "Clients" }, - { to: "/services", label: "Services" }, - { to: "/staff", label: "Staff" }, - { to: "/invoices", label: "Invoices" }, - { to: "/group-bookings", label: "Group Bookings" }, - { to: "/reports", label: "Reports" }, - { to: "/portal", label: "Customer Portal" }, + { to: "/admin", label: "Appointments" }, + { to: "/admin/clients", label: "Clients" }, + { to: "/admin/services", label: "Services" }, + { to: "/admin/staff", label: "Staff" }, + { to: "/admin/invoices", label: "Invoices" }, + { to: "/admin/group-bookings", label: "Group Bookings" }, + { to: "/admin/reports", label: "Reports" }, + { to: "/", label: "Customer Portal" }, ]; function AdminLayout() { @@ -36,7 +36,7 @@ function AdminLayout() { > Groom Book {NAV_LINKS.map(({ to, label }) => { const active = - to === "/" ? location.pathname === "/" : location.pathname.startsWith(to); + to === "/admin" + ? location.pathname === "/admin" + : location.pathname.startsWith(to); return ( ; + if (location.pathname.startsWith("/admin")) { + return ( + + } /> + + ); } - return ; + return ; } diff --git a/apps/web/src/__tests__/App.test.tsx b/apps/web/src/__tests__/App.test.tsx index 4854db3..cfd422d 100644 --- a/apps/web/src/__tests__/App.test.tsx +++ b/apps/web/src/__tests__/App.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen, within, act } from "@testing-library/react"; +import { render, screen, within } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { App } from "../App.js"; @@ -11,30 +11,28 @@ beforeEach(() => { } as unknown as Response); }); -async function renderApp(route = "/") { - await act(async () => { - render( - - - - ); - }); +function renderApp(route = "/admin") { + render( + + + + ); return screen.getByRole("navigation"); } describe("App navigation", () => { - it("renders the Groom Book brand", async () => { - const nav = await renderApp(); + it("renders the Groom Book brand", () => { + const nav = renderApp(); expect(within(nav).getByText("Groom Book")).toBeInTheDocument(); }); - it("renders the Book CTA button", async () => { - const nav = await renderApp(); + it("renders the Book CTA button", () => { + const nav = renderApp(); expect(within(nav).getByText("Book")).toBeInTheDocument(); }); - it("renders all primary nav links", async () => { - const nav = await renderApp(); + it("renders all primary nav links", () => { + const nav = renderApp(); const expectedLinks = [ "Appointments", "Clients", @@ -49,10 +47,20 @@ describe("App navigation", () => { }); }); - it("highlights the active route link", async () => { - const nav = await renderApp("/clients"); + it("highlights the active route link", () => { + const nav = renderApp("/admin/clients"); const clientsLink = within(nav).getByText("Clients"); // Active links use fontWeight 600 expect(clientsLink).toHaveStyle({ fontWeight: "600" }); }); + + it("renders customer portal at root", () => { + render( + + + + ); + // Customer portal should render at root - no admin nav present + expect(screen.queryByText("Groom Book")).not.toBeInTheDocument(); + }); });