UAT findings landed across the last few Playwright + React Grab passes; single grouped commit so the index doesn't fragment into 30 one-liners. User & auth: - `user-settings`: name now updates the avatar + topbar menu after save (was reading stale session). - `me/password-reset`: 3 bugs (token validation, error response shape, redirect chain). - Admin user permission-overrides route honours the same envelope as the rest of the admin surface. Dashboard: - Removed obsolete `revenue-breakdown-chart` + `dashboard-widgets-card` (replaced by the customisable widget grid). - Strip `revenue_breakdown` from analytics route + use-analytics + service + integration test so nothing renders an empty card. - Activity log timeline overshoot fix (`interest-timeline` + `entity-activity-feed`). - Tightened tiles: active-deals, berth-heat-widget, pipeline-value, kpi-tile. - `dev-mode-banner`: derive dismissed state synchronously instead of via an effect (set-state-in-effect lint rule). Forms & lists (assorted polish): - client / company / yacht / interest / reminder forms — validation + empty-state copy + tab transitions. - companies/yachts list tweaks; berth recommender panel; qualification checklist; supplemental info request button. Infra & misc: - Queue workers (ai / email / notifications) — log shape + per-job timeout consistency. - Auth / brochures / users schema small adjustments; seeds reflect permissions matrix changes. - Scan shell + scanner manifest + AI admin page small fixes. - `next.config.transpilePackages` adds `echarts`/`zrender`/`echarts-for-react` (recommended config from echarts-for-react inside Next). Docs: - `docs/superpowers/audits/alpha-uat-master.md` — single rolling cross-cutting UAT findings doc (per CLAUDE.md convention). - `docs/BACKLOG.md`: dashboard stats cards (§I) + activity-log normalization (§J). - 2026-05-18 audit log updated with this batch. - `CLAUDE.md` — small manual UAT scaffold notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { AlertTriangle, X } from 'lucide-react';
|
|
|
|
import { apiFetch } from '@/lib/api/client';
|
|
|
|
interface DevFlags {
|
|
emailRedirectTo: string | null;
|
|
isDev: boolean;
|
|
}
|
|
|
|
const DISMISS_KEY = 'pn-crm.devBanner.dismissed';
|
|
|
|
/**
|
|
* Single-line warning banner shown across the app whenever a dev-mode
|
|
* safety net is active (today: `EMAIL_REDIRECT_TO`). Sticky at the top
|
|
* of every authenticated surface so reps and admins can't miss that
|
|
* every outbound email is being rerouted to a single inbox.
|
|
*
|
|
* Production hides the banner entirely because env.ts refuses to boot
|
|
* with EMAIL_REDIRECT_TO set when NODE_ENV=production — the flag is
|
|
* only ever non-null in dev / staging.
|
|
*
|
|
* Dismissal is persisted in localStorage keyed by the redirect address —
|
|
* changing `EMAIL_REDIRECT_TO` re-shows the banner so the new target
|
|
* can't be silently inherited.
|
|
*/
|
|
export function DevModeBanner() {
|
|
const { data } = useQuery<{ data: DevFlags }>({
|
|
queryKey: ['internal', 'dev-flags'],
|
|
queryFn: () => apiFetch<{ data: DevFlags }>('/api/v1/internal/dev-flags'),
|
|
staleTime: 5 * 60_000,
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
|
|
const redirect = data?.data?.emailRedirectTo;
|
|
const [overrideDismissed, setOverrideDismissed] = useState(false);
|
|
const persistedDismissed =
|
|
typeof window !== 'undefined' && !!redirect
|
|
? window.localStorage.getItem(DISMISS_KEY) === redirect
|
|
: false;
|
|
const dismissed = overrideDismissed || persistedDismissed;
|
|
|
|
if (!redirect || dismissed) return null;
|
|
|
|
const handleDismiss = () => {
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(DISMISS_KEY, redirect);
|
|
}
|
|
setOverrideDismissed(true);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
role="alert"
|
|
className="flex items-center justify-center gap-2 border-b border-amber-300 bg-amber-50 px-3 py-1.5 text-xs font-medium text-amber-900"
|
|
title={`Every outbound email is rewritten so the recipient is ${redirect}. The original address is preserved in the recipient name as "(was: original@...)". Unset EMAIL_REDIRECT_TO in your env to disable.`}
|
|
>
|
|
<AlertTriangle className="size-3.5 shrink-0" aria-hidden />
|
|
<span>
|
|
Dev mode: outbound emails redirected to <code className="font-mono">{redirect}</code>
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={handleDismiss}
|
|
aria-label="Dismiss dev mode banner"
|
|
className="ml-2 inline-flex size-5 shrink-0 items-center justify-center rounded hover:bg-amber-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-500"
|
|
>
|
|
<X className="size-3.5" aria-hidden />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|