chore(autonomous-session): consolidate uncommitted work from prior session

Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
This commit is contained in:
2026-05-23 00:52:59 +02:00
parent 43719b49e9
commit 221ae5784e
749 changed files with 7440 additions and 3118 deletions

View File

@@ -0,0 +1,247 @@
/**
* Registry of every transactional template the system can emit, with a
* pre-baked sample-prop fixture so an admin can fire a realistic
* preview to a designated address without needing to trigger the real
* upstream flow (a real signing send, a real portal invite, etc.).
*
* Consumed by `<TestTemplateCard>` (admin → Email page) and
* `/api/v1/admin/email/test-template`. New templates land here once
* they're plumbed; the UI dropdown reflects the registry at runtime so
* adding an entry surfaces it without any UI change.
*/
import { activationEmail, resetEmail } from '@/lib/email/templates/portal-auth';
import { crmInviteEmail } from '@/lib/email/templates/crm-invite';
import { adminEmailChangeEmail } from '@/lib/email/templates/admin-email-change';
import { notificationDigestEmail } from '@/lib/email/templates/notification-digest';
import {
signingInvitationEmail,
signingCompletedEmail,
signingReminderEmail,
signingCancelledEmail,
} from '@/lib/email/templates/document-signing';
import { inquiryClientConfirmation } from '@/lib/email/templates/inquiry-client-confirmation';
import { inquirySalesNotification } from '@/lib/email/templates/inquiry-sales-notification';
import {
residentialClientConfirmation,
residentialSalesAlert,
} from '@/lib/email/templates/residential-inquiry';
export type RenderedEmail = { subject: string; html: string; text?: string };
export interface TestTemplateMeta {
/** Stable id - used as the dropdown value + the POST body key. */
id: string;
/** Human-facing dropdown label. */
label: string;
/** One-line description shown under the dropdown to clarify which
* real flow fires this template in production. */
description: string;
/** Renders a fully-formed email with placeholder data baked in. */
render: (sample: SampleContext) => Promise<RenderedEmail>;
}
/**
* Shared sample fixture passed to every renderer so the previewed
* subject/body line up with the admin's current port. Real flows
* resolve these from DB lookups; the tester injects synthetic but
* plausible values instead.
*/
export interface SampleContext {
recipientName: string;
recipientEmail: string;
portName: string;
portUrl: string;
}
export const TEST_TEMPLATES: TestTemplateMeta[] = [
{
id: 'portal_activation',
label: 'Portal · Activation invite',
description: 'Fires when an admin invites a client to activate their portal account.',
render: (s) =>
activationEmail({
recipientName: s.recipientName,
portName: s.portName,
link: `${s.portUrl}/portal/activate/sample-token`,
ttlHours: 24,
}),
},
{
id: 'portal_reset',
label: 'Portal · Password reset',
description: 'Fires when a portal user requests a password reset link.',
render: (s) =>
resetEmail({
recipientName: s.recipientName,
portName: s.portName,
link: `${s.portUrl}/portal/reset/sample-token`,
ttlMinutes: 120,
}),
},
{
id: 'crm_invite',
label: 'CRM · Teammate invitation',
description: 'Fires when a super-admin invites a new teammate to the CRM.',
render: (s) =>
crmInviteEmail({
recipientName: s.recipientName,
portName: s.portName,
isSuperAdmin: false,
link: `${s.portUrl}/invite/sample-token`,
ttlHours: 72,
}),
},
{
id: 'admin_email_change',
label: 'CRM · Admin email change confirmation',
description: 'Fires when an admin updates their CRM login email - confirmation step.',
render: (s) =>
adminEmailChangeEmail({
recipientName: s.recipientName,
portName: s.portName,
newEmail: s.recipientEmail,
changedByDisplayName: 'Sample Admin',
loginUrl: `${s.portUrl}/login`,
}),
},
{
id: 'notification_digest',
label: 'Reminders · Notification digest',
description: 'Fires on the configured cadence (daily/weekly) with the reps open reminders.',
render: (s) =>
notificationDigestEmail({
recipientName: s.recipientName,
portName: s.portName,
items: [
{
type: 'reminder',
title: 'Follow up with Matthew Ciaccio on Berth A1',
description: 'Reservation EOI sent 5 days ago - no response yet.',
link: `${s.portUrl}/clients/sample-client-id`,
createdAt: new Date(Date.now() - 86_400_000),
},
{
type: 'alert',
title: 'Berth B12 PDF parse failed',
description: null,
link: `${s.portUrl}/berths/sample-berth-id`,
createdAt: new Date(Date.now() - 2 * 86_400_000),
},
],
totalUnread: 2,
inboxLink: `${s.portUrl}/inbox`,
}),
},
{
id: 'signing_invitation',
label: 'Documenso · Signing invitation',
description: 'Fires when the rep dispatches the first signing-invite email for a doc.',
render: (s) =>
signingInvitationEmail({
recipientName: s.recipientName,
portName: s.portName,
documentLabel: 'Sales Contract',
signerRole: 'client',
signingUrl: `${s.portUrl}/sign/sample-token`,
senderName: 'Sample Sales Manager',
customMessage: null,
}),
},
{
id: 'signing_reminder',
label: 'Documenso · Signing reminder',
description: 'Fires when a manual reminder is dispatched for an outstanding signer.',
render: (s) =>
signingReminderEmail({
recipientName: s.recipientName,
portName: s.portName,
documentLabel: 'Sales Contract',
signingUrl: `${s.portUrl}/sign/sample-token`,
invitedAgo: '5 days ago',
customMessage: null,
}),
},
{
id: 'signing_completed',
label: 'Documenso · Fully signed notification',
description: 'Fires when every required signer has signed and the document is complete.',
render: (s) =>
signingCompletedEmail({
recipientName: s.recipientName,
portName: s.portName,
documentLabel: 'Sales Contract',
clientName: s.recipientName,
completedAt: new Date(),
}),
},
{
id: 'signing_cancelled',
label: 'Documenso · Signing cancelled',
description: 'Fires when the rep cancels a document mid-signature with notify-recipients.',
render: (s) =>
signingCancelledEmail({
recipientName: s.recipientName,
portName: s.portName,
documentLabel: 'Sales Contract',
reason: 'Customer renegotiated terms; a fresh contract will follow.',
}),
},
{
id: 'inquiry_client_confirmation',
label: 'Public inquiry · Client confirmation',
description: 'Fires when a public-site visitor submits the contact form (their copy).',
render: (s) =>
inquiryClientConfirmation({
firstName: s.recipientName.split(' ')[0] ?? s.recipientName,
mooringNumber: 'A1',
contactEmail: 'sales@portnimara.com',
portName: s.portName,
}),
},
{
id: 'inquiry_sales_notification',
label: 'Public inquiry · Sales notification',
description: 'Fires alongside the client confirmation - alerts the sales rep to a new lead.',
render: (s) =>
inquirySalesNotification({
fullName: s.recipientName,
email: s.recipientEmail,
phone: '+1 555 0100',
mooringNumber: 'A1',
crmUrl: `${s.portUrl}/clients/sample-client-id`,
portName: s.portName,
}),
},
{
id: 'residential_client_confirmation',
label: 'Residential inquiry · Client confirmation',
description: 'Fires when a residential-site visitor submits the contact form.',
render: (s) =>
residentialClientConfirmation({
firstName: s.recipientName.split(' ')[0] ?? s.recipientName,
contactEmail: 'sales@portnimara.com',
portName: s.portName,
}),
},
{
id: 'residential_sales_alert',
label: 'Residential inquiry · Sales alert',
description: 'Fires alongside the residential client confirmation - alerts the sales team.',
render: (s) =>
residentialSalesAlert({
fullName: s.recipientName,
email: s.recipientEmail,
phone: '+1 555 0100',
placeOfResidence: 'Monaco',
preferredContactMethod: 'email',
notes: 'Looking for year-round mooring + marina apartment access.',
crmDeepLink: `${s.portUrl}/residential/clients/sample-id`,
portName: s.portName,
}),
},
];
export function findTestTemplate(id: string): TestTemplateMeta | undefined {
return TEST_TEMPLATES.find((t) => t.id === id);
}