feat(uat-b1): ship Wave A-E of Bucket 1 audit findings

Wave A (Interest+EOI form quick wins):
- Auto-select yacht after inline-create from interest form
- EOI generate dialog: "View EOI" action toast
- Interest form berth picker: formatBerthRange compact label
- Remove "Generate EOI" button from Documents tab (clean removal)
- Interest auto-assign: only sales_agent/sales_manager auto-claim
  ownership on create (explicit role check via user_port_roles join)
- LinkedBerthRowItem dims: drop "D" suffix + "L × W" format
- ExternalEoiUploadDialog: prefillSignatories prop threaded from
  active EOI signers
- EOI signature progress on Overview milestone card footer

Wave B (a11y + i18n sweeps):
- aria-live on supplemental-info error state
- text-[10px] -> text-xs in client-pipeline-summary
- Currency formatter: locale default removed (Intl uses runtime)
- en-US/en-GB hardcoded toLocaleString swept across 13 components

Wave C (Primary berth always in EOI bundle):
- Service guard strengthened on update path
- Migration 0083 backfills historical primary rows

Wave D (Onboarding super_admin discoverability):
- /api/v1/admin/onboarding/status endpoint + shared service
- Topbar OnboardingBanner (super_admin, session-dismissible)
- OnboardingTile dashboard widget (rail group, self-hides at 100%)
- Celebration toast + invalidate of shared status on last tick

Wave E (Branded post-completion email idempotency):
- Verified handleDocumentCompleted already owns the email fan-out
- Added regression test for the polling path + idempotency

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:40:37 +02:00
parent 41737fa950
commit 14ae41d0fa
40 changed files with 835 additions and 70 deletions

View File

@@ -0,0 +1,44 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api/client';
export interface OnboardingStatusStep {
id: string;
href: string;
label: string;
description: string;
done: boolean;
auto: boolean;
}
export interface OnboardingStatusPayload {
steps: OnboardingStatusStep[];
completed: number;
total: number;
percent: number;
isComplete: boolean;
nextStep: { id: string; label: string; href: string } | null;
}
/**
* Shared onboarding-status query. Drives the topbar banner, dashboard tile,
* and the admin checklist summary. Cached for 60s so all three surfaces
* share a single fetch on first paint.
*
* Pass `enabled=false` to skip the network call (e.g. when the current
* user isn't a super_admin and the surface won't render anyway).
*/
export function useOnboardingStatus(opts: { enabled?: boolean } = {}) {
return useQuery<OnboardingStatusPayload>({
queryKey: ['admin', 'onboarding-status'],
queryFn: () =>
apiFetch<{ data: OnboardingStatusPayload }>('/api/v1/admin/onboarding/status').then(
(r) => r.data,
),
staleTime: 60_000,
enabled: opts.enabled ?? true,
retry: false,
});
}