diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index a8f7b2a..c8301c1 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -249,6 +249,11 @@ export function App() { return ; } + // Dev mode: staff users should not land on the customer portal — redirect to admin + if (authDisabled && getDevUser()?.type === "staff") { + return ; + } + // Show login BEFORE checking needsSetup (needsSetup is never set for unauthenticated users) if (!authDisabled && !session) { return ; diff --git a/apps/web/src/portal/CustomerPortal.tsx b/apps/web/src/portal/CustomerPortal.tsx index 80d4e5b..f3d567a 100644 --- a/apps/web/src/portal/CustomerPortal.tsx +++ b/apps/web/src/portal/CustomerPortal.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, useEffect, useRef } from "react"; -import { useSearchParams } from "react-router-dom"; +import { Navigate, useSearchParams } from "react-router-dom"; import { Home, Calendar, PawPrint, FileText, CreditCard, MessageSquare, Settings, LogOut, Shield, @@ -37,6 +37,8 @@ export function CustomerPortal() { const [rescheduleAppointment, setRescheduleAppointment] = useState | null>(null); const [session, setSession] = useState(null); const [sessionExtended, setSessionExtended] = useState(false); + const [sessionError, setSessionError] = useState(null); + const [isInitializing, setIsInitializing] = useState(true); const [clientName, setClientName] = useState(""); const { branding } = useBranding(); const [searchParams, setSearchParams] = useSearchParams(); @@ -68,7 +70,8 @@ export function CustomerPortal() { }) .catch(() => { setSearchParams({}, { replace: true }); - }); + }) + .finally(() => setIsInitializing(false)); return; } @@ -81,16 +84,26 @@ export function CustomerPortal() { body: JSON.stringify({ clientId: devUser.id }), }) .then((r) => { - if (!r.ok) return null; + if (!r.ok) { + setSessionError("Failed to create portal session. Please try again."); + return null; + } return r.json() as Promise; }) .then((s) => { if (s && s.id) { setSession(s); setClientName(devUser.name); + setSessionError(null); } }) - .catch(() => {}); + .catch(() => { + setSessionError("Failed to connect. Please check your connection and try again."); + }) + .finally(() => setIsInitializing(false)); + } else { + // No sessionId param and no dev user — init is complete with no session + setIsInitializing(false); } }, []); @@ -168,6 +181,11 @@ export function CustomerPortal() { const avatarInitials = (clientName.split(" ")[0] || "G").charAt(0).toUpperCase(); + // Redirect to login if init is complete and no valid session exists + if (!isInitializing && !session) { + return ; + } + return (
+ {sessionError && ( +
+

{sessionError}

+
+ )} {renderSection()}
diff --git a/apps/web/src/portal/sections/Dashboard.tsx b/apps/web/src/portal/sections/Dashboard.tsx index 43abe5c..f252e8a 100644 --- a/apps/web/src/portal/sections/Dashboard.tsx +++ b/apps/web/src/portal/sections/Dashboard.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from "react"; +import { Navigate } from "react-router-dom"; import { Calendar, Clock, PawPrint, CreditCard, Star, ChevronRight, AlertTriangle } from "lucide-react"; interface DashboardProps { @@ -183,13 +184,7 @@ export function Dashboard({ } if (!sessionId) { - return ( -
-
-

Please sign in to view your dashboard.

-
-
- ); + return ; } const upcomingAppointments = getUpcomingAppointments();