fix(portal): prevent Dashboard redirect during impersonation session load
When navigating to /?sessionId=xxx, Dashboard would immediately redirect to /login because sessionId was null before the fetch completed. The impersonation banner never rendered. Add isImpersonating state: true while impersonation fetch is in-flight, prevents Dashboard from redirecting until session loads. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -42,6 +42,9 @@ export function CustomerPortal() {
|
|||||||
// Track whether we've attempted to fetch a session — used to prevent premature redirect
|
// Track whether we've attempted to fetch a session — used to prevent premature redirect
|
||||||
// when a session fetch is in-flight (E2E mocks resolve synchronously, batched with setInitComplete)
|
// when a session fetch is in-flight (E2E mocks resolve synchronously, batched with setInitComplete)
|
||||||
const [sessionAttempted, setSessionAttempted] = useState(false);
|
const [sessionAttempted, setSessionAttempted] = useState(false);
|
||||||
|
// Track whether an impersonation session fetch from URL param is in-flight
|
||||||
|
// Dashboard will not redirect while this is true, allowing the session to load
|
||||||
|
const [isImpersonating, setIsImpersonating] = useState(false);
|
||||||
const { branding } = useBranding();
|
const { branding } = useBranding();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ export function CustomerPortal() {
|
|||||||
const sessionId = searchParams.get("sessionId");
|
const sessionId = searchParams.get("sessionId");
|
||||||
|
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
|
setIsImpersonating(true);
|
||||||
// Real impersonation session from URL param
|
// Real impersonation session from URL param
|
||||||
fetch(`/api/impersonation/sessions/${sessionId}`)
|
fetch(`/api/impersonation/sessions/${sessionId}`)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
@@ -75,7 +79,7 @@ export function CustomerPortal() {
|
|||||||
setSessionAttempted(true);
|
setSessionAttempted(true);
|
||||||
setSearchParams({}, { replace: true });
|
setSearchParams({}, { replace: true });
|
||||||
})
|
})
|
||||||
.finally(() => setInitComplete(true));
|
.finally(() => { setInitComplete(true); setIsImpersonating(false); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +166,7 @@ export function CustomerPortal() {
|
|||||||
const sessionId = session?.id ?? null;
|
const sessionId = session?.id ?? null;
|
||||||
switch (activeSection) {
|
switch (activeSection) {
|
||||||
case "dashboard":
|
case "dashboard":
|
||||||
return <Dashboard onNavigate={handleNavClick} readOnly={!!isReadOnly} sessionId={sessionId} clientName={clientName} onReschedule={handleReschedule} />;
|
return <Dashboard onNavigate={handleNavClick} readOnly={!!isReadOnly} sessionId={sessionId} clientName={clientName} onReschedule={handleReschedule} isImpersonating={isImpersonating} />;
|
||||||
case "appointments":
|
case "appointments":
|
||||||
return <AppointmentsSection readOnly={!!isReadOnly} sessionId={sessionId} />;
|
return <AppointmentsSection readOnly={!!isReadOnly} sessionId={sessionId} />;
|
||||||
case "pets":
|
case "pets":
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ interface DashboardProps {
|
|||||||
onNavigate: (section: "appointments" | "pets" | "billing" | "reports") => void;
|
onNavigate: (section: "appointments" | "pets" | "billing" | "reports") => void;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
onReschedule: (appointmentId: string) => void;
|
onReschedule: (appointmentId: string) => void;
|
||||||
|
/** True when a sessionId param was in the URL and the session is still loading */
|
||||||
|
isImpersonating?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Appointment {
|
interface Appointment {
|
||||||
@@ -73,6 +75,7 @@ export function Dashboard({
|
|||||||
onNavigate,
|
onNavigate,
|
||||||
readOnly,
|
readOnly,
|
||||||
onReschedule,
|
onReschedule,
|
||||||
|
isImpersonating,
|
||||||
}: DashboardProps) {
|
}: DashboardProps) {
|
||||||
const [appointments, setAppointments] = useState<Appointment[]>([]);
|
const [appointments, setAppointments] = useState<Appointment[]>([]);
|
||||||
const [pets, setPets] = useState<Pet[]>([]);
|
const [pets, setPets] = useState<Pet[]>([]);
|
||||||
@@ -183,7 +186,7 @@ export function Dashboard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId && !isImpersonating) {
|
||||||
return <Navigate to="/login" replace />;
|
return <Navigate to="/login" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user