From 6abb5a0c2ab6cdb20a133be3b613d1df4a97209b Mon Sep 17 00:00:00 2001
From: DevCalebR
Date: Mon, 20 Apr 2026 12:34:07 -0400
Subject: [PATCH] Fix admin support shortcut routing
---
app/admin/[businessId]/open-customer/route.ts | 40 ++++++++++++++++
app/admin/[businessId]/page.tsx | 27 +++++++----
app/admin/[businessId]/workspace/page.tsx | 28 ++++++++---
app/admin/exit-customer-mode/route.ts | 31 +++++++++++++
app/admin/page.tsx | 29 ++++++++++--
app/app/layout.tsx | 46 +++++++++++++++----
app/app/settings/actions.ts | 10 ++--
lib/admin-customer-context.ts | 33 +++++++++++++
lib/admin-customer-paths.ts | 23 ++++++++++
lib/auth.ts | 11 +++++
tests/admin-customer-context.test.ts | 30 ++++++++++++
tests/admin-operator-routes.test.ts | 12 +++++
tests/tenant-isolation-wiring.test.ts | 2 +-
13 files changed, 283 insertions(+), 39 deletions(-)
create mode 100644 app/admin/[businessId]/open-customer/route.ts
create mode 100644 app/admin/exit-customer-mode/route.ts
create mode 100644 lib/admin-customer-context.ts
create mode 100644 lib/admin-customer-paths.ts
create mode 100644 tests/admin-customer-context.test.ts
diff --git a/app/admin/[businessId]/open-customer/route.ts b/app/admin/[businessId]/open-customer/route.ts
new file mode 100644
index 0000000..8c0dd52
--- /dev/null
+++ b/app/admin/[businessId]/open-customer/route.ts
@@ -0,0 +1,40 @@
+import { NextResponse } from 'next/server';
+
+import { ADMIN_CUSTOMER_BUSINESS_COOKIE } from '@/lib/admin-customer-context';
+import { resolveSafeAdminCustomerAppPath } from '@/lib/admin-customer-paths';
+import { getAdminSession } from '@/lib/admin';
+import { db } from '@/lib/db';
+
+export async function GET(request: Request, { params }: { params: { businessId: string } }) {
+ const adminSession = await getAdminSession();
+ const requestUrl = new URL(request.url);
+
+ if (!adminSession?.userId) {
+ return NextResponse.redirect(new URL('/sign-in', requestUrl));
+ }
+
+ if (!adminSession.isAdmin) {
+ return NextResponse.redirect(new URL('/app', requestUrl));
+ }
+
+ const business = await db.business.findUnique({
+ where: { id: params.businessId },
+ select: { id: true },
+ });
+
+ if (!business) {
+ return NextResponse.redirect(new URL('/admin?error=Business%20not%20found.', requestUrl));
+ }
+
+ const nextPath = resolveSafeAdminCustomerAppPath(requestUrl.searchParams.get('path'));
+ const response = NextResponse.redirect(new URL(nextPath, requestUrl));
+
+ response.cookies.set(ADMIN_CUSTOMER_BUSINESS_COOKIE, business.id, {
+ httpOnly: true,
+ sameSite: 'lax',
+ secure: process.env.NODE_ENV === 'production',
+ path: '/app',
+ });
+
+ return response;
+}
diff --git a/app/admin/[businessId]/page.tsx b/app/admin/[businessId]/page.tsx
index d5d9ca9..711c62c 100644
--- a/app/admin/[businessId]/page.tsx
+++ b/app/admin/[businessId]/page.tsx
@@ -13,6 +13,7 @@ import {
sendBusinessTestSmsAction,
setBusinessProvisioningStatusAction,
} from '@/app/admin/actions';
+import { buildAdminCustomerOpenHref } from '@/lib/admin-customer-paths';
import {
buildAdminOnboardingConfidence,
canDeleteTestBusiness,
@@ -180,9 +181,9 @@ function getOperatorEventDetails(value: unknown) {
function buildOperatorEventRelatedHref(businessId: string, relatedEntityType: string | null, relatedEntityId: string | null) {
if (!relatedEntityType || !relatedEntityId) return null;
- if (relatedEntityType === 'lead') return `/admin/${businessId}/workspace#recent-leads`;
+ if (relatedEntityType === 'lead') return buildAdminCustomerOpenHref(businessId, `/app/leads/${relatedEntityId}`);
if (relatedEntityType === 'message') return `/admin/${businessId}/workspace#recent-activity`;
- if (relatedEntityType === 'call') return `/admin/${businessId}/workspace#call-flow-snapshot`;
+ if (relatedEntityType === 'call') return buildAdminCustomerOpenHref(businessId, '/app/call-flow');
return null;
}
@@ -495,12 +496,15 @@ export default async function AdminBusinessDetailPage({
-
+
Open customer workspace
-
+
Open customer leads
+
+ View support workspace snapshot
+
Back to board
@@ -670,22 +674,25 @@ export default async function AdminBusinessDetailPage({
Support mode shortcuts
- Safe customer-side entry points without impersonation or tenant bleed.
+ Open the real customer pages for this business, or choose the snapshot view when you only need read-only context.
-
+
Open customer workspace
-
+
Open customer leads
-
+
Open customer settings
-
+
Open customer call flow
+
+ View support workspace snapshot
+
- Support mode stays read-only. Use it to inspect leads, settings, and call flow quickly without weakening business isolation.
+ Support workspace snapshots stay read-only. The buttons above open the real customer pages in an admin-scoped customer mode so you can act without impersonation.
diff --git a/app/admin/[businessId]/workspace/page.tsx b/app/admin/[businessId]/workspace/page.tsx
index 0af13d7..cdacc0a 100644
--- a/app/admin/[businessId]/workspace/page.tsx
+++ b/app/admin/[businessId]/workspace/page.tsx
@@ -1,6 +1,7 @@
import Link from 'next/link';
import { notFound } from 'next/navigation';
+import { buildAdminCustomerOpenHref } from '@/lib/admin-customer-paths';
import { buildAdminNextStep } from '@/lib/admin-dashboard';
import { requireAdmin } from '@/lib/admin';
import { getAdminOwnerState } from '@/lib/admin-provisioning';
@@ -79,7 +80,7 @@ export default async function AdminBusinessWorkspacePage({ params }: { params: {
{business.name} support mode workspace
- Read-only customer context so the founder can inspect leads, settings, and call flow without impersonation.
+ Read-only customer snapshot for fast inspection. Use the customer-mode buttons below when you need the real editable customer pages.
@@ -96,7 +97,7 @@ export default async function AdminBusinessWorkspacePage({ params }: { params: {
Support snapshot
- Immediate health signal plus quick jumps into the customer context that matter most.
+ Immediate health signal plus clear separation between real customer pages and read-only snapshot sections.
@@ -122,17 +123,32 @@ export default async function AdminBusinessWorkspacePage({ params }: { params: {
-
+
+ Open customer workspace
+
+
Open customer leads
-
+
Open customer settings
-
+
Open customer call flow
+
+
+
+
+ View leads snapshot
+
+
+ View settings snapshot
+
+
+ View call flow snapshot
+
- Open recent activity
+ View recent activity snapshot
diff --git a/app/admin/exit-customer-mode/route.ts b/app/admin/exit-customer-mode/route.ts
new file mode 100644
index 0000000..42a41dc
--- /dev/null
+++ b/app/admin/exit-customer-mode/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+
+import { ADMIN_CUSTOMER_BUSINESS_COOKIE } from '@/lib/admin-customer-context';
+import { getAdminSession } from '@/lib/admin';
+
+export async function GET(request: Request) {
+ const adminSession = await getAdminSession();
+ const requestUrl = new URL(request.url);
+
+ if (!adminSession?.userId) {
+ return NextResponse.redirect(new URL('/sign-in', requestUrl));
+ }
+
+ if (!adminSession.isAdmin) {
+ return NextResponse.redirect(new URL('/app', requestUrl));
+ }
+
+ const businessId = requestUrl.searchParams.get('businessId')?.trim();
+ const redirectPath = businessId ? `/admin/${businessId}` : '/admin';
+ const response = NextResponse.redirect(new URL(redirectPath, requestUrl));
+
+ response.cookies.set(ADMIN_CUSTOMER_BUSINESS_COOKIE, '', {
+ httpOnly: true,
+ sameSite: 'lax',
+ secure: process.env.NODE_ENV === 'production',
+ path: '/app',
+ maxAge: 0,
+ });
+
+ return response;
+}
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
index 1cb042f..1ebe135 100644
--- a/app/admin/page.tsx
+++ b/app/admin/page.tsx
@@ -10,6 +10,7 @@ import {
restoreBusinessAction,
sendBusinessTestSmsAction,
} from '@/app/admin/actions';
+import { buildAdminCustomerOpenHref } from '@/lib/admin-customer-paths';
import {
adminBoardFilterOptions,
buildAdminBusinessPickerLabel,
@@ -566,12 +567,24 @@ export default async function AdminPage({ searchParams }: { searchParams?: Recor
Open business
-
- Open workspace
+
+ Open customer workspace
+
+
+ Open customer settings
Open full advanced controls
+
+ View support workspace snapshot
+
@@ -723,12 +736,18 @@ export default async function AdminPage({ searchParams }: { searchParams?: Recor
Open business
-
- Open workspace
+
+ Open customer workspace
-
+
Open customer leads
+
+ View support workspace snapshot
+
{!isBusinessArchived(business) ? (