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 <noreply@paperclip.ing>
This commit is contained in:
@@ -360,6 +360,12 @@ export function ClientsPage() {
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "0.5rem", marginLeft: "auto" }}>
|
||||
<a
|
||||
href={`/?impersonate=true&clientName=${encodeURIComponent(selectedClient.name)}&staffName=${encodeURIComponent("Staff")}&reason=${encodeURIComponent(`Support view for ${selectedClient.name}`)}`}
|
||||
style={{ ...btnStyle, backgroundColor: "#fef3c7", color: "#92400e", borderColor: "#fde68a", textDecoration: "none", display: "inline-flex", alignItems: "center", gap: "0.3rem" }}
|
||||
>
|
||||
👁 View as Customer
|
||||
</a>
|
||||
<button onClick={() => openEditClient(selectedClient)} style={btnStyle}>
|
||||
Edit client
|
||||
</button>
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user