fix(uat): batch — timeline overshoot, name-sync, reset-password, dashboard cleanup, queue/seed hygiene + alpha UAT findings doc

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>
This commit is contained in:
2026-05-20 15:56:11 +02:00
parent 8c669e2918
commit 449b9497ab
59 changed files with 1831 additions and 631 deletions

View File

@@ -19,6 +19,9 @@ interface AppShellProps {
isSuperAdmin: boolean;
user: NonNullable<SidebarProps['user']>;
ports: TopbarProps['ports'];
/** Per-port logo URLs resolved server-side. Sidebar picks the entry
* matching the currently-active port from the UI store. */
portLogoUrls: Record<string, string | null>;
/**
* Server-rendered form-factor hint (from the request User-Agent). The
* shell mounts the matching tree on first render so we never paint the
@@ -59,6 +62,7 @@ export function AppShell({
isSuperAdmin,
user,
ports,
portLogoUrls,
initialFormFactor,
children,
}: AppShellProps) {
@@ -83,7 +87,13 @@ export function AppShell({
<MobileTopbar />
</>
) : (
<Sidebar portRoles={portRoles} isSuperAdmin={isSuperAdmin} user={user} ports={ports} />
<Sidebar
portRoles={portRoles}
isSuperAdmin={isSuperAdmin}
user={user}
ports={ports}
portLogoUrls={portLogoUrls}
/>
);
const footer = isMobile ? (

View File

@@ -41,15 +41,16 @@ import type { UserPortRole } from '@/lib/db/schema/users';
import type { Role } from '@/lib/db/schema/users';
import type { Port } from '@/lib/db/schema/ports';
const LOGO_URL =
'https://s3.portnimara.com/images/Port%20Nimara%20New%20Logo-Circular%20Frame_250px.png';
interface SidebarProps {
portRoles: (UserPortRole & { port: { id: string; slug: string; name: string }; role: Role })[];
isSuperAdmin?: boolean;
user?: { name: string; email: string };
/** Ports the user has access to. Drives the footer port switcher. */
ports?: Port[];
/** Per-port logo URLs resolved server-side in the dashboard layout.
* The sidebar header swaps to the current port's logo via the UI
* store's `currentPortId`. Null entries render the wordmark fallback. */
portLogoUrls?: Record<string, string | null>;
}
interface NavItem {
@@ -103,6 +104,22 @@ function buildNavSections(portSlug: string | undefined): NavSection[] {
},
],
},
{
title: 'Insights',
marinaRequired: true,
umamiRequired: true,
items: [
// Marketing / Umami integration. Distinct from the main dashboard
// (which is sales-focused) so the audience and the metrics don't
// compete for visual real estate. Whole section is hidden when
// Umami isn't wired up — see SidebarContent.
{
href: `${base}/website-analytics`,
label: 'Website analytics',
icon: Globe,
},
],
},
{
title: 'Documents',
marinaRequired: true,
@@ -127,22 +144,6 @@ function buildNavSections(portSlug: string | undefined): NavSection[] {
},
],
},
{
title: 'Insights',
marinaRequired: true,
umamiRequired: true,
items: [
// Marketing / Umami integration. Distinct from the main dashboard
// (which is sales-focused) so the audience and the metrics don't
// compete for visual real estate. Whole section is hidden when
// Umami isn't wired up — see SidebarContent.
{
href: `${base}/website-analytics`,
label: 'Website analytics',
icon: Globe,
},
],
},
{
title: 'Communication',
marinaRequired: true,
@@ -236,6 +237,8 @@ function SidebarContent({
hasResidentialAccess,
user,
ports,
currentPort,
currentLogoUrl,
onToggleCollapse,
}: {
collapsed: boolean;
@@ -247,6 +250,8 @@ function SidebarContent({
hasResidentialAccess: boolean;
user?: SidebarProps['user'];
ports?: Port[];
currentPort: Port | null;
currentLogoUrl: string | null;
/** When provided, renders the collapse toggle row above the user footer (desktop). */
onToggleCollapse?: () => void;
}) {
@@ -295,15 +300,21 @@ function SidebarContent({
collapsed ? 'h-16 px-2' : 'h-24 px-4',
)}
>
<Image
src={LOGO_URL}
alt="Port Nimara"
width={collapsed ? 40 : 72}
height={collapsed ? 40 : 72}
className="rounded-full shadow-sm"
unoptimized
priority
/>
{currentLogoUrl ? (
<Image
src={currentLogoUrl}
alt={currentPort?.name ?? 'Logo'}
width={collapsed ? 40 : 72}
height={collapsed ? 40 : 72}
className="rounded-full shadow-sm"
unoptimized
priority
/>
) : (
<div className="flex h-12 items-center px-3 text-sm font-semibold text-slate-700">
{currentPort?.name ?? 'CRM'}
</div>
)}
{onToggleCollapse && (
<button
type="button"
@@ -439,12 +450,21 @@ function SidebarContent({
);
}
export function Sidebar({ portRoles, isSuperAdmin = false, user, ports }: SidebarProps) {
export function Sidebar({
portRoles,
isSuperAdmin = false,
user,
ports,
portLogoUrls,
}: SidebarProps) {
// Sidebar collapse removed — design preference is the always-expanded
// form. Forcibly false; the store flag stays for backwards-compat with
// any code still reading it.
const sidebarCollapsed = false;
const currentPortSlug = useUIStore((s) => s.currentPortSlug);
const currentPortId = useUIStore((s) => s.currentPortId);
const currentPort = ports?.find((p) => p.id === currentPortId) ?? ports?.[0] ?? null;
const currentLogoUrl = currentPortId ? (portLogoUrls?.[currentPortId] ?? null) : null;
// Super admins see every section regardless of role rows.
const hasAdminAccess =
@@ -478,6 +498,8 @@ export function Sidebar({ portRoles, isSuperAdmin = false, user, ports }: Sideba
hasResidentialAccess={hasResidentialAccess}
user={user}
ports={ports}
currentPort={currentPort}
currentLogoUrl={currentLogoUrl}
/>
</aside>
);