fix(audit-wave-9): standardize on Sheet for previews; doctrine in CLAUDE.md

Swap the one outlier (client-interests-tab.tsx) from Vaul Drawer to
Sheet side=right so every detail-preview surface uses the same
primitive. Document the doctrine: Sheet for side panels on both desktop
and mobile; Vaul Drawer reserved for mobile-only bottom-sheet UX
(currently just MoreSheet).

Closes ui/ux M11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 11:50:07 +02:00
parent b2588ecdd8
commit 4233aa3ac3
94 changed files with 1674 additions and 895 deletions

View File

@@ -13,6 +13,10 @@ interface AdminEmailChangeData {
interface RenderOpts {
branding?: BrandingShell | null;
/** Admin override for the email subject. Falls back to the template's
* default when null/empty. Wired through from email_templates.subject
* via getTemplateOverridesForKey() in mailer-config.ts. */
subject?: string | null;
}
function AdminEmailChangeBody({
@@ -78,7 +82,9 @@ export async function adminEmailChangeEmail(
overrides?: RenderOpts,
): Promise<{ subject: string; html: string; text: string }> {
const portName = data.portName ?? 'Port Nimara';
const subject = `An administrator updated your ${portName} sign-in email`;
const subject = overrides?.subject?.trim()
? overrides.subject
: `An administrator updated your ${portName} sign-in email`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(

View File

@@ -15,6 +15,7 @@ interface InviteData {
interface RenderOpts {
branding?: BrandingShell | null;
subject?: string | null;
}
function InviteBody({
@@ -85,7 +86,9 @@ export async function crmInviteEmail(
overrides?: RenderOpts,
): Promise<{ subject: string; html: string; text: string }> {
const portName = data.portName ?? 'Port Nimara';
const subject = `You're invited to the ${portName} CRM`;
const subject = overrides?.subject?.trim()
? overrides.subject
: `You're invited to the ${portName} CRM`;
const role = data.isSuperAdmin ? 'super administrator' : 'administrator';
const accent = brandingPrimaryColor(overrides?.branding);

View File

@@ -12,6 +12,7 @@ export interface InquiryClientConfirmationData {
interface RenderOpts {
branding?: BrandingShell | null;
subject?: string | null;
}
function ClientConfirmationBody({
@@ -61,9 +62,11 @@ export async function inquiryClientConfirmation(
const { firstName, mooringNumber, contactEmail } = data;
const portName = data.portName ?? 'Port Nimara';
const berthText = mooringNumber ? `Berth ${mooringNumber}` : `a ${portName} Berth`;
const subject = mooringNumber
? `Thank You for Your Interest in Berth ${mooringNumber}`
: `Thank You for Your Interest in a ${portName} Berth`;
const subject = overrides?.subject?.trim()
? overrides.subject
: mooringNumber
? `Thank You for Your Interest in Berth ${mooringNumber}`
: `Thank You for Your Interest in a ${portName} Berth`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(

View File

@@ -14,6 +14,7 @@ export interface InquirySalesNotificationData {
interface RenderOpts {
branding?: BrandingShell | null;
subject?: string | null;
}
function SalesNotificationBody({
@@ -75,7 +76,7 @@ export async function inquirySalesNotification(
) {
const portName = data.portName ?? 'Port Nimara';
const mooringDisplay = data.mooringNumber || 'None';
const subject = `New Interest - ${portName}`;
const subject = overrides?.subject?.trim() ? overrides.subject : `New Interest - ${portName}`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(

View File

@@ -19,6 +19,7 @@ interface DigestData {
interface RenderOpts {
branding?: BrandingShell | null;
subject?: string | null;
}
const TYPE_LABELS: Record<string, string> = {
@@ -117,7 +118,9 @@ export async function notificationDigestEmail(
data: DigestData,
overrides?: RenderOpts,
): Promise<{ subject: string; html: string; text: string }> {
const subject = `${data.portName} CRM digest — ${data.totalUnread} unread`;
const subject = overrides?.subject?.trim()
? overrides.subject
: `${data.portName} CRM digest — ${data.totalUnread} unread`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(<DigestBody {...data} accent={accent} />, { pretty: false });

View File

@@ -5,6 +5,7 @@ import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '
interface RenderOpts {
branding?: BrandingShell | null;
subject?: string | null;
}
export interface ResidentialClientConfirmationData {
@@ -60,7 +61,9 @@ export async function residentialClientConfirmation(
overrides?: RenderOpts,
) {
const portName = data.portName ?? 'Port Nimara';
const subject = `Thank You for Your Interest - ${portName} Residences`;
const subject = overrides?.subject?.trim()
? overrides.subject
: `Thank You for Your Interest - ${portName} Residences`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(
<ClientConfirmationBody
@@ -178,7 +181,9 @@ export async function residentialSalesAlert(
overrides?: RenderOpts,
) {
const portName = data.portName ?? 'Port Nimara';
const subject = `New Residential Inquiry - ${data.fullName}`;
const subject = overrides?.subject?.trim()
? overrides.subject
: `New Residential Inquiry - ${data.fullName}`;
const accent = brandingPrimaryColor(overrides?.branding);
const body = await render(<SalesAlertBody portName={portName} data={data} accent={accent} />, {
pretty: false,