From 0a5f085a9ecc860e52cb29f624e7e2488f88b3f2 Mon Sep 17 00:00:00 2001 From: Matt Ciaccio Date: Wed, 6 May 2026 22:28:41 +0200 Subject: [PATCH] fix(audit): wire reminder defaults into createInterest; doc branding gap (R2-H15/H16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R2-H16: /admin/reminders persisted defaultEnabled + defaultDays to system_settings but createInterest ignored them — every new interest defaulted to reminderEnabled=false regardless. The validator now treats reminderEnabled / reminderDays as optional (no default false), and createInterest falls back to getPortReminderConfig(portId) when the caller omits them. Explicit false / null still opts out. R2-H15: branding admin (/admin/branding) saves 5 settings that no code reads — the email templates and BrandedAuthShell hardcode Port Nimara branding. Wiring it end-to-end is a multi-template refactor; documented the gap inline above getPortBrandingConfig with a step-by-step wire-up plan so future devs don't think it's done. The reminder-digest scheduler (digestEnabled/digestTime/digestTimezone) remains unimplemented — needs a new BullMQ recurring job that batches pending reminders into per-user/per-port digest emails. Out of scope for this audit pass. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/services/interests.service.ts | 10 ++++++++++ src/lib/services/port-config.ts | 19 +++++++++++++++++++ src/lib/validators/interests.ts | 5 ++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/lib/services/interests.service.ts b/src/lib/services/interests.service.ts index 642c5ba..6b9896d 100644 --- a/src/lib/services/interests.service.ts +++ b/src/lib/services/interests.service.ts @@ -9,6 +9,7 @@ import { yachts } from '@/lib/db/schema/yachts'; import { companyMemberships } from '@/lib/db/schema/companies'; import { tags } from '@/lib/db/schema/system'; import { createAuditLog, type AuditMeta } from '@/lib/audit'; +import { getPortReminderConfig } from '@/lib/services/port-config'; import { NotFoundError, ConflictError, ValidationError } from '@/lib/errors'; import { emitToRoom } from '@/lib/socket/server'; import { setEntityTags } from '@/lib/services/entity-tags.helper'; @@ -438,12 +439,21 @@ export async function createInterest(portId: string, data: CreateInterestInput, data.yachtId, ); + // Per-port reminder defaults — applied only when the caller omitted + // reminderEnabled / reminderDays. Honors the /admin/reminders page. + const reminderConfig = await getPortReminderConfig(portId); + const resolvedReminderEnabled = interestData.reminderEnabled ?? reminderConfig.defaultEnabled; + const resolvedReminderDays = + interestData.reminderDays ?? (resolvedReminderEnabled ? reminderConfig.defaultDays : null); + const result = await withTransaction(async (tx) => { const [interest] = await tx .insert(interests) .values({ portId, ...interestData, + reminderEnabled: resolvedReminderEnabled, + reminderDays: resolvedReminderDays, leadCategory: resolvedLeadCategory, }) .returning(); diff --git a/src/lib/services/port-config.ts b/src/lib/services/port-config.ts index e407ab1..d539dd4 100644 --- a/src/lib/services/port-config.ts +++ b/src/lib/services/port-config.ts @@ -237,6 +237,25 @@ export async function listDocumensoWebhookSecrets(): Promise` component in + * `src/components/shared/branded-auth-shell.tsx` still hardcode the + * `s3.portnimara.com` logo URL and the Port Nimara color palette. A + * second port wired into this CRM will see Port Nimara branding in + * every transactional email until those consumers call + * `getPortBrandingConfig(portId)`. Tracked as audit finding R2-H15. + * + * To wire fully: + * 1. Take `branding` config as a server-side prop into + * `` (pass it from the page server component). + * 2. Refactor the email shell helper in each `templates/*.ts` module + * to take `headerHtml` / `footerHtml` / `primaryColor` instead of + * the inline constants. + * 3. In each sender, call `getPortBrandingConfig(portId)` and thread + * the branding values into the template call. + */ export interface PortBrandingConfig { logoUrl: string | null; primaryColor: string; diff --git a/src/lib/validators/interests.ts b/src/lib/validators/interests.ts index fc76663..68303a3 100644 --- a/src/lib/validators/interests.ts +++ b/src/lib/validators/interests.ts @@ -35,7 +35,10 @@ export const createInterestSchema = z.object({ source: z.string().optional(), notes: z.string().optional(), tagIds: z.array(z.string()).optional().default([]), - reminderEnabled: z.boolean().optional().default(false), + // Omitting reminderEnabled / reminderDays falls back to the per-port + // defaults configured at /admin/reminders (resolved in + // createInterest). To opt out explicitly pass false / null. + reminderEnabled: z.boolean().optional(), reminderDays: z.number().int().min(1).optional(), desiredLengthFt: optionalDesiredDimSchema, desiredWidthFt: optionalDesiredDimSchema,