From 0de06d8a2f78a2669d6cf910da5468fec148cd7c Mon Sep 17 00:00:00 2001 From: William Date: Wed, 4 Mar 2026 18:16:24 +0000 Subject: [PATCH] Add persistent header with role switcher and lazy provisioning - Create AppHeader component with title, DEMO badge, RoleSwitcher, user info, and sign-out - Integrate AppHeader in layout.tsx inside the provider tree - RoleSwitcher now navigates to the corresponding role page on switch - Home page role cards call setRole() before navigating - ProductionAuthProvider responds to role changes and eagerly fetches vendor/delivery IDs - Add lazy provisioning: auto-create vendor/delivery records when user switches roles - Handle null vendorId/deliveryPersonId gracefully with loading spinners - Guard useVendorOrders/useDeliveryAssignments against empty string UUIDs - Add RLS INSERT policies for vendors, delivery_persons, and users tables Co-Authored-By: Claude Opus 4.6 --- src/app/delivery/page.tsx | 13 +- src/app/layout.tsx | 7 +- src/app/page.tsx | 262 +++++++++----------- src/app/vendor/page.tsx | 13 +- src/components/shared/app-header.tsx | 46 ++++ src/components/shared/role-switcher.tsx | 15 +- src/hooks/use-auth.tsx | 103 ++++++-- src/hooks/use-orders.tsx | 8 + supabase/migrations/003_insert_policies.sql | 11 + 9 files changed, 307 insertions(+), 171 deletions(-) create mode 100644 src/components/shared/app-header.tsx create mode 100644 supabase/migrations/003_insert_policies.sql diff --git a/src/app/delivery/page.tsx b/src/app/delivery/page.tsx index 6d18e7d..2241f3b 100644 --- a/src/app/delivery/page.tsx +++ b/src/app/delivery/page.tsx @@ -17,9 +17,20 @@ interface DeliveryOrder extends OrderWithDetails { export default function DeliveryDashboard() { const { deliveryPersonId } = useAuth(); - const { assignments, loading, error } = useDeliveryAssignments(deliveryPersonId!); + const { assignments, loading, error } = useDeliveryAssignments(deliveryPersonId ?? ''); const [updating, setUpdating] = useState(null); + if (!deliveryPersonId) { + return ( +
+
+
+

Setting up delivery account...

+
+
+ ); + } + const handleStatusUpdate = async ( assignmentId: string, newStatus: 'accepted' | 'picked_up' | 'delivered' diff --git a/src/app/layout.tsx b/src/app/layout.tsx index eb1009a..8678480 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import "./globals.css"; import { RoleProvider } from "@/hooks/use-role"; import { AuthProvider } from "@/hooks/use-auth"; import { CartProvider } from "@/hooks/use-cart"; +import { AppHeader } from "@/components/shared/app-header"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -38,13 +39,9 @@ export default function RootLayout({ - {process.env.NEXT_PUBLIC_DEMO_MODE === 'true' && ( -
- DEMO MODE -
- )} + {children} diff --git a/src/app/page.tsx b/src/app/page.tsx index bb3b838..bb49d51 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,15 +1,19 @@ 'use client'; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { ShoppingBag, Store, Truck, LogOut } from "lucide-react"; +import { ShoppingBag, Store, Truck } from "lucide-react"; import { DEMO_MODE } from "@/lib/config/demo"; import { useAuth } from "@/hooks/use-auth"; +import { useRole } from "@/hooks/use-role"; +import type { UserRole } from "@/types"; const roles = [ { name: "Customer", + role: "customer" as UserRole, description: "Browse local vendors, add items to your cart, and place orders for delivery.", href: "/vendors", icon: ShoppingBag, @@ -19,6 +23,7 @@ const roles = [ }, { name: "Vendor", + role: "vendor" as UserRole, description: "View incoming orders, update order status, and communicate with customers.", href: "/vendor", icon: Store, @@ -28,6 +33,7 @@ const roles = [ }, { name: "Delivery", + role: "delivery" as UserRole, description: "Accept delivery requests, navigate to pickups, and complete deliveries.", href: "/delivery", icon: Truck, @@ -38,178 +44,150 @@ const roles = [ ]; export default function Home() { - const { user, loading, signOut } = useAuth(); + const { user, loading } = useAuth(); + const { setRole } = useRole(); + const router = useRouter(); + + const handleRoleClick = (role: UserRole, href: string) => { + setRole(role); + router.push(href); + }; if (!DEMO_MODE) { // Authenticated user: show role-based navigation if (user && !loading) { return ( -
-
-
-

Project Firefly

-
- {user.name} - -
-
-
- -
-
-
-

- Welcome back, {user.name} -

-

- Select a role to continue -

-
- -
- {roles.map((role) => ( - - - -
- -
- {role.name} -
- - - {role.description} - - -
- - ))} -
-
-
-
- ); - } - - // Not authenticated: show sign-in / create account - return ( -
-
-
-

Project Firefly

-
-
-
-
-
+
+

- Community-powered food ordering for local co-ops + Welcome back, {user.name}

- Order from local vendors, delivered by your neighbors + Select a role to continue

-
+
{roles.map((role) => ( -
-
- -
-

{role.name}

-
+ ))}
- -
- - - - - - - - - -
-
- ); - } - - return ( -
- {/* Header */} -
-
-

Project Firefly Demo

-
-
+ ); + } - {/* Main Content */} + // Not authenticated: show sign-in / create account + return (
-
- {/* Description */} -
+
+

Community-powered food ordering for local co-ops

- Select a role below to explore the demo + Order from local vendors, delivered by your neighbors

- {/* Role Cards */} -
+
{roles.map((role) => ( - - - -
- -
- {role.name} -
- - - {role.description} - - -
- +
+
+ +
+

{role.name}

+
))}
- {/* Instructions */} - - - How to Test the Full Flow - - -
    -
  1. Open 3 browser tabs (or use incognito windows)
  2. -
  3. In Tab 1: Click Customer → Browse vendors and place an order
  4. -
  5. In Tab 2: Click Vendor → Watch for incoming orders and accept them
  6. -
  7. In Tab 3: Click Delivery → Accept the delivery and mark it complete
  8. -
-

- All roles see real-time updates via Supabase. Changes in one tab appear instantly in others. -

-
-
+
+ + + + + + + + + +
-
+ ); + } + + // Demo mode + return ( +
+
+
+

+ Community-powered food ordering for local co-ops +

+

+ Select a role below to explore the demo +

+
+ +
+ {roles.map((role) => ( + + ))} +
+ + + + How to Test the Full Flow + + +
    +
  1. Open 3 browser tabs (or use incognito windows)
  2. +
  3. In Tab 1: Click Customer → Browse vendors and place an order
  4. +
  5. In Tab 2: Click Vendor → Watch for incoming orders and accept them
  6. +
  7. In Tab 3: Click Delivery → Accept the delivery and mark it complete
  8. +
+

+ All roles see real-time updates via Supabase. Changes in one tab appear instantly in others. +

+
+
+
+
); } diff --git a/src/app/vendor/page.tsx b/src/app/vendor/page.tsx index 9c2d328..7afa78a 100644 --- a/src/app/vendor/page.tsx +++ b/src/app/vendor/page.tsx @@ -18,9 +18,20 @@ interface VendorOrder extends OrderWithDetails { export default function VendorDashboard() { const { vendorId } = useAuth(); - const { orders, loading, error } = useVendorOrders(vendorId!); + const { orders, loading, error } = useVendorOrders(vendorId ?? ''); const [updating, setUpdating] = useState(null); + if (!vendorId) { + return ( +
+
+
+

Setting up vendor account...

+
+
+ ); + } + const handleAcceptOrder = async (orderId: string) => { setUpdating(orderId); try { diff --git a/src/components/shared/app-header.tsx b/src/components/shared/app-header.tsx new file mode 100644 index 0000000..95e391d --- /dev/null +++ b/src/components/shared/app-header.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { useAuth } from '@/hooks/use-auth'; +import { DEMO_MODE } from '@/lib/config/demo'; +import { RoleSwitcher } from '@/components/shared/role-switcher'; +import { Button } from '@/components/ui/button'; +import { LogOut } from 'lucide-react'; + +export function AppHeader() { + const { user, loading, signOut } = useAuth(); + + const showRoleSwitcher = DEMO_MODE || (!loading && user); + + return ( +
+
+
+

Project Firefly

+ {DEMO_MODE && ( + + DEMO + + )} +
+ + {showRoleSwitcher && ( +
+ {!DEMO_MODE && user && ( + <> + + {user.name} + + + + )} + +
+ )} +
+
+ ); +} diff --git a/src/components/shared/role-switcher.tsx b/src/components/shared/role-switcher.tsx index e841505..a5b3070 100644 --- a/src/components/shared/role-switcher.tsx +++ b/src/components/shared/role-switcher.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useRouter } from 'next/navigation'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Sheet, @@ -12,6 +13,12 @@ import { useRole } from '@/hooks/use-role'; import type { UserRole } from '@/types'; import { cn } from '@/lib/utils'; +const roleRoutes: Record = { + customer: '/vendors', + vendor: '/vendor', + delivery: '/delivery', +}; + interface RoleSwitcherProps { userInitials?: string; avatarUrl?: string; @@ -39,6 +46,12 @@ export function RoleSwitcher({ availableRoles = ['customer', 'vendor', 'delivery'], }: RoleSwitcherProps) { const { currentRole, setRole, roleColor } = useRole(); + const router = useRouter(); + + const handleRoleSwitch = (role: UserRole) => { + setRole(role); + router.push(roleRoutes[role]); + }; return ( @@ -73,7 +86,7 @@ export function RoleSwitcher({ {availableRoles.map((role) => (