From 65eb405cf1cee636279ff66b960362c7e79e4764 Mon Sep 17 00:00:00 2001 From: jason Date: Sat, 21 Mar 2026 07:27:30 -0500 Subject: [PATCH] Rename roles, add multi-vendor support, and Events system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roles: owner→admin, manager→vendor, cashier→user across all routes, seed, and client UI. Role badge colours updated in UsersPage. Multi-vendor: - GET /vendors and GET /users now return all records for admin role; vendor/user roles remain scoped to their vendorId - POST /users: admin can specify vendorId to assign user to any vendor - vendors/users now include vendor name in responses for admin context Events (new): - Prisma schema: Event, EventTax, EventProduct models; Transaction.eventId - POST/GET/PUT/DELETE /api/v1/events — full CRUD, vendor-scoped - PUT /events/:id/taxes + DELETE — upsert/remove per-event tax rate overrides - POST/GET/DELETE /events/:id/products — product allowlist (empty=all) - GET /events/:id/transactions — paginated list scoped to event - GET /events/:id/reports/summary — revenue, avg tx, top products for event - Transactions: eventId accepted in both single POST and batch POST - Catalog sync: active/upcoming events included in /catalog/sync response Client: - Layout nav filtered by role (user role sees Catalog only) - Dashboard cards filtered by role - Events page: list, create/edit modal, detail modal with Configuration (tax overrides + product allowlist) and Reports tabs DB: DATABASE_URL updated to file:./prisma/dev.db in .env.example Co-Authored-By: Claude Sonnet 4.6 --- client/src/App.tsx | 2 + client/src/components/Layout.tsx | 15 +- client/src/pages/DashboardPage.tsx | 15 +- client/src/pages/EventsPage.tsx | 466 +++++++++++++++++++++++++++++ client/src/pages/UsersPage.tsx | 6 +- server/.env.example | 2 +- server/prisma/schema.prisma | 56 +++- server/prisma/seed.ts | 18 +- server/src/app.ts | 2 + server/src/routes/catalog.ts | 19 +- server/src/routes/categories.ts | 8 +- server/src/routes/events.ts | 374 +++++++++++++++++++++++ server/src/routes/products.ts | 8 +- server/src/routes/taxes.ts | 8 +- server/src/routes/transactions.ts | 16 +- server/src/routes/users.ts | 50 ++-- server/src/routes/vendors.ts | 27 +- 17 files changed, 1014 insertions(+), 78 deletions(-) create mode 100644 client/src/pages/EventsPage.tsx create mode 100644 server/src/routes/events.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index c6eb1ce..44400f4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -7,6 +7,7 @@ import UsersPage from "./pages/UsersPage"; import CatalogPage from "./pages/CatalogPage"; import VendorPage from "./pages/VendorPage"; import ReportsPage from "./pages/ReportsPage"; +import EventsPage from "./pages/EventsPage"; function ProtectedRoute({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth(); @@ -43,6 +44,7 @@ export default function App() { > } /> } /> + } /> } /> } /> } /> diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index 1f1cba3..b8a23a1 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -3,11 +3,12 @@ import { NavLink, Outlet, useNavigate } from "react-router-dom"; import { useAuth } from "../context/AuthContext"; const NAV = [ - { to: "/", label: "Dashboard", exact: true }, - { to: "/catalog", label: "Catalog" }, - { to: "/users", label: "Users" }, - { to: "/vendor", label: "Vendor" }, - { to: "/reports", label: "Reports" }, + { to: "/", label: "Dashboard", exact: true, roles: ["admin", "vendor", "user"] }, + { to: "/catalog", label: "Catalog", roles: ["admin", "vendor", "user"] }, + { to: "/events", label: "Events", roles: ["admin", "vendor"] }, + { to: "/users", label: "Users", roles: ["admin", "vendor"] }, + { to: "/vendor", label: "Vendor", roles: ["admin", "vendor"] }, + { to: "/reports", label: "Reports", roles: ["admin", "vendor"] }, ]; export default function Layout() { @@ -19,12 +20,14 @@ export default function Layout() { navigate("/login", { replace: true }); }; + const visibleNav = NAV.filter((item) => item.roles.includes(user?.role ?? "")); + return (