From b97f52386e3cc7709b2c5c81ff980d17ee9646aa Mon Sep 17 00:00:00 2001 From: Groom Book CTO Date: Thu, 19 Mar 2026 12:11:55 +0000 Subject: [PATCH] feat(web): add "View as Customer" impersonation button on staff Clients page Staff can now click "View as Customer" on any client profile in the admin panel. This navigates to the customer portal with impersonation auto-activated, showing the portal exactly as that customer would see it (read-only, with full audit trail). The portal reads impersonate/clientName/reason/staffName from URL search params on mount, auto-starts the impersonation session, then cleans up the URL. Co-Authored-By: Paperclip --- apps/web/src/pages/Clients.tsx | 6 ++++++ apps/web/src/portal/CustomerPortal.tsx | 23 ++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/web/src/pages/Clients.tsx b/apps/web/src/pages/Clients.tsx index 50df051..242d33a 100644 --- a/apps/web/src/pages/Clients.tsx +++ b/apps/web/src/pages/Clients.tsx @@ -360,6 +360,12 @@ export function ClientsPage() { )}
+ + 👁 View as Customer + diff --git a/apps/web/src/portal/CustomerPortal.tsx b/apps/web/src/portal/CustomerPortal.tsx index e70543a..bb77072 100644 --- a/apps/web/src/portal/CustomerPortal.tsx +++ b/apps/web/src/portal/CustomerPortal.tsx @@ -1,4 +1,4 @@ -import { useState, useReducer, useCallback } from "react"; +import { useState, useReducer, useCallback, useEffect } from "react"; import { Home, Calendar, PawPrint, FileText, CreditCard, MessageSquare, Settings, Eye, LogOut, Clock, Shield, @@ -101,6 +101,27 @@ export function CustomerPortal() { const [impersonation, dispatchImpersonation] = useReducer(impersonationReducer, null); const { branding } = useBranding(); + // Auto-start impersonation from URL params (staff flow from admin panel). + // Runs once on mount only — impersonation state is managed by the reducer after init. + const [impersonationInitDone, setImpersonationInitDone] = useState(false); + useEffect(() => { + if (impersonationInitDone) return; + const params = new URLSearchParams(window.location.search); + if (params.get("impersonate") === "true") { + const clientName = params.get("clientName") || "Unknown Customer"; + const reason = params.get("reason") || `Viewing portal as ${clientName}`; + const staffName = params.get("staffName") || "Staff"; + dispatchImpersonation({ + type: "START", + staffName, + staffRole: "Admin", + reason, + }); + window.history.replaceState({}, "", window.location.pathname); + } + setImpersonationInitDone(true); + }, [impersonationInitDone]); + const logPageView = useCallback((page: string) => { if (impersonation?.active) { dispatchImpersonation({