feat(launch-readiness-batch): UAT drains, navigation refactor, launch infra, trackers
Bundles the rest of the in-flight work from this UAT round into one
checkpoint. Each sub-area is independent; see the headings below.
UAT polish (drained 11 findings from active-uat.md):
- Dialog primitive default bumped sm:max-w-xl/lg:max-w-3xl →
sm:max-w-2xl/lg:max-w-4xl so multi-field forms + PDF previews
aren't cramped at 1440-1920px.
- Notes tab badge aggregation: new countFor{Client,Yacht,Company}
Aggregated helpers in notes.service mirror the listFor*Aggregated
symmetric-reach joins. yacht-tabs + company-tabs render the
badge; client-tabs already had badge support.
- Supplemental-info form polish bundle: BrandedAuthShell gains a
`width: 'sm' | 'md'` prop (md uses min-h-dvh scroll instead of
fixed inset-0 pin so long forms scroll naturally). Form picks up
port branding (logoUrl + backgroundUrl + appName) via
loadByToken. Address fields completed (street + city + region +
postal + country). Port name eyebrow + success-state copy added.
- new-document-menu Upload-file landing toast: per-file completion
emits toast.success with action link to the destination entity
or folder.
- interest-tabs OverviewTab "from client" pill on Email + Phone
rows via new EditableRow `inheritedFrom` prop.
- create-document-wizard subject picker → segmented button strip
(5 types visible at once).
Launch infra:
- UTM column wiring (Init 1b step 4): migration
0089_website_submissions_utm.sql adds utm_source/medium/campaign/
term/content + composite index (port_id, utm_source, received_at)
for per-campaign rollups. website-inquiries intake accepts the
five fields. Residential intake intentionally untouched per audit
scope.
- Invoicing module gate (Init 1c spike): new
invoices-module.service + invoices layout guard + registry entry
invoices_module_enabled (default false). Audit conclusion in
launch-readiness.md: payments table is canonical money path;
/invoices flow is parallel infrastructure now hidden by default.
Smart-back navigation refactor:
- Replaced breadcrumb component with history-aware Back button.
New route-labels.ts + use-smart-back hook +
navigation-history-tracker so back falls through to the parent
route when there's no prior page in history.
- Sidebar / topbar / mobile-topbar adopt the new pattern; old
breadcrumb-store kept for back-compat consumers but the
breadcrumbs component is gone.
- 6 detail pages (admin/errors per-id + codes, invoices/
upload-receipts, reports kind, tenancies detail, analytics
metric, client detail) migrated.
Trackers + docs:
- docs/launch-readiness.md — master pre-launch tracker. Includes
the reports gap audit (cross-cutting filter set, Marketing +
Financial blockers, custom builder remaining entities, scheduled
CSV/XLSX, template scope picker).
- docs/superpowers/audits/active-uat.md — 15 findings flipped
OPEN → SHIPPED locally with fix-applied notes; 4 OPEN remaining
(each blocked on user input or cross-repo).
- CLAUDE.md — minor session notes carried forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
Building2,
|
||||
Receipt,
|
||||
FileText,
|
||||
FileBarChart,
|
||||
Inbox,
|
||||
Camera,
|
||||
Globe,
|
||||
@@ -55,6 +56,11 @@ interface SidebarProps {
|
||||
/** Per-port `tenancies_module_enabled` resolution. Gates the Tenancies
|
||||
* sidebar entry. Resolved server-side in the dashboard layout. */
|
||||
tenanciesModuleByPort?: Record<string, boolean>;
|
||||
/** Per-port `expenses_module_enabled` resolution. Gates the Expenses
|
||||
* + How-to-upload-receipts sidebar entries. Resolved server-side in
|
||||
* the dashboard layout. Defaults to true (feature on) per port when
|
||||
* the map is missing for the active port. */
|
||||
expensesModuleByPort?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
@@ -80,6 +86,12 @@ interface NavItemGated extends NavItem {
|
||||
/** When true, only render this item if the tenancies module is enabled
|
||||
* for the current port. Resolved against `tenanciesModuleByPort`. */
|
||||
requiresTenanciesModule?: boolean;
|
||||
/** When true, only render this item if the expenses module is enabled
|
||||
* for the current port. Resolved against `expensesModuleByPort`. */
|
||||
requiresExpensesModule?: boolean;
|
||||
/** When true, only render this item if Umami analytics is wired up
|
||||
* for the port. */
|
||||
umamiRequired?: boolean;
|
||||
}
|
||||
|
||||
function buildNavSections(portSlug: string | undefined): NavSection[] {
|
||||
@@ -123,17 +135,26 @@ function buildNavSections(portSlug: string | undefined): NavSection[] {
|
||||
{
|
||||
title: 'Insights',
|
||||
marinaRequired: true,
|
||||
umamiRequired: true,
|
||||
items: [
|
||||
// Reports surface (dashboard / clients / berths / interests
|
||||
// builders, plus templates / schedules / runs). Routes existed
|
||||
// since the report-builder ship but the sidebar entry was never
|
||||
// wired - reps had to land here via direct link.
|
||||
{
|
||||
href: `${base}/reports`,
|
||||
label: 'Reports',
|
||||
icon: FileBarChart,
|
||||
},
|
||||
// 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.
|
||||
// compete for visual real estate. Hidden when Umami isn't wired
|
||||
// up via the per-item umamiRequired flag below.
|
||||
{
|
||||
href: `${base}/website-analytics`,
|
||||
label: 'Website analytics',
|
||||
icon: Globe,
|
||||
},
|
||||
umamiRequired: true,
|
||||
} as NavItemGated,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -145,7 +166,12 @@ function buildNavSections(portSlug: string | undefined): NavSection[] {
|
||||
title: 'Financial',
|
||||
marinaRequired: true,
|
||||
items: [
|
||||
{ href: `${base}/expenses`, label: 'Expenses', icon: Receipt },
|
||||
{
|
||||
href: `${base}/expenses`,
|
||||
label: 'Expenses',
|
||||
icon: Receipt,
|
||||
requiresExpensesModule: true,
|
||||
} as NavItemGated,
|
||||
// Invoices nav entry removed - the expense-to-PDF flow is the
|
||||
// only invoicing surface now (employee expense reports). The
|
||||
// standalone /invoices route still exists for any back-compat
|
||||
@@ -157,7 +183,8 @@ function buildNavSections(portSlug: string | undefined): NavSection[] {
|
||||
href: `${base}/invoices/upload-receipts`,
|
||||
label: 'How to upload receipts',
|
||||
icon: Camera,
|
||||
},
|
||||
requiresExpensesModule: true,
|
||||
} as NavItemGated,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -252,6 +279,7 @@ function SidebarContent({
|
||||
hasMarinaAccess,
|
||||
hasResidentialAccess,
|
||||
tenanciesModuleEnabled,
|
||||
expensesModuleEnabled,
|
||||
user,
|
||||
ports,
|
||||
currentPort,
|
||||
@@ -266,6 +294,7 @@ function SidebarContent({
|
||||
hasMarinaAccess: boolean;
|
||||
hasResidentialAccess: boolean;
|
||||
tenanciesModuleEnabled: boolean;
|
||||
expensesModuleEnabled: boolean;
|
||||
user?: SidebarProps['user'];
|
||||
ports?: Port[];
|
||||
currentPort: Port | null;
|
||||
@@ -389,6 +418,8 @@ function SidebarContent({
|
||||
const gated = item as NavItemGated;
|
||||
if (gated.requiresTenanciesModule && !tenanciesModuleEnabled)
|
||||
return false;
|
||||
if (gated.requiresExpensesModule && !expensesModuleEnabled) return false;
|
||||
if (gated.umamiRequired && !umamiConfigured) return false;
|
||||
return true;
|
||||
})
|
||||
.map((item) => (
|
||||
@@ -482,6 +513,7 @@ export function Sidebar({
|
||||
ports,
|
||||
portLogoUrls,
|
||||
tenanciesModuleByPort,
|
||||
expensesModuleByPort,
|
||||
}: SidebarProps) {
|
||||
// Sidebar collapse removed - design preference is the always-expanded
|
||||
// form. Forcibly false; the store flag stays for backwards-compat with
|
||||
@@ -494,6 +526,12 @@ export function Sidebar({
|
||||
const tenanciesModuleEnabled = currentPortId
|
||||
? (tenanciesModuleByPort?.[currentPortId] ?? false)
|
||||
: false;
|
||||
// Expenses defaults to enabled when the port's entry is missing - the
|
||||
// registry default is `true`, so a port that's never explicitly
|
||||
// toggled the feature should keep it visible.
|
||||
const expensesModuleEnabled = currentPortId
|
||||
? (expensesModuleByPort?.[currentPortId] ?? true)
|
||||
: true;
|
||||
|
||||
// Super admins see every section regardless of role rows.
|
||||
const hasAdminAccess =
|
||||
@@ -526,6 +564,7 @@ export function Sidebar({
|
||||
hasMarinaAccess={hasMarinaAccess}
|
||||
hasResidentialAccess={hasResidentialAccess}
|
||||
tenanciesModuleEnabled={tenanciesModuleEnabled}
|
||||
expensesModuleEnabled={expensesModuleEnabled}
|
||||
user={user}
|
||||
ports={ports}
|
||||
currentPort={currentPort}
|
||||
|
||||
Reference in New Issue
Block a user