fix(uat): batch — timeline overshoot, name-sync, reset-password, dashboard cleanup, queue/seed hygiene + alpha UAT findings doc
UAT findings landed across the last few Playwright + React Grab passes; single grouped commit so the index doesn't fragment into 30 one-liners. User & auth: - `user-settings`: name now updates the avatar + topbar menu after save (was reading stale session). - `me/password-reset`: 3 bugs (token validation, error response shape, redirect chain). - Admin user permission-overrides route honours the same envelope as the rest of the admin surface. Dashboard: - Removed obsolete `revenue-breakdown-chart` + `dashboard-widgets-card` (replaced by the customisable widget grid). - Strip `revenue_breakdown` from analytics route + use-analytics + service + integration test so nothing renders an empty card. - Activity log timeline overshoot fix (`interest-timeline` + `entity-activity-feed`). - Tightened tiles: active-deals, berth-heat-widget, pipeline-value, kpi-tile. - `dev-mode-banner`: derive dismissed state synchronously instead of via an effect (set-state-in-effect lint rule). Forms & lists (assorted polish): - client / company / yacht / interest / reminder forms — validation + empty-state copy + tab transitions. - companies/yachts list tweaks; berth recommender panel; qualification checklist; supplemental info request button. Infra & misc: - Queue workers (ai / email / notifications) — log shape + per-job timeout consistency. - Auth / brochures / users schema small adjustments; seeds reflect permissions matrix changes. - Scan shell + scanner manifest + AI admin page small fixes. - `next.config.transpilePackages` adds `echarts`/`zrender`/`echarts-for-react` (recommended config from echarts-for-react inside Next). Docs: - `docs/superpowers/audits/alpha-uat-master.md` — single rolling cross-cutting UAT findings doc (per CLAUDE.md convention). - `docs/BACKLOG.md`: dashboard stats cards (§I) + activity-log normalization (§J). - 2026-05-18 audit log updated with this batch. - `CLAUDE.md` — small manual UAT scaffold notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,12 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
|
||||
const primaryBerth = await getPrimaryBerth(interestId);
|
||||
const berthMooring = primaryBerth?.mooringNumber ?? null;
|
||||
|
||||
// Resolve the port branding app name once so template-fallback drafts
|
||||
// sign off as "{Port} Team" instead of leaking another tenant's name.
|
||||
const { getPortBrandingConfig } = await import('@/lib/services/port-config');
|
||||
const portBrand = await getPortBrandingConfig(portId).catch(() => null);
|
||||
const brandingAppName = portBrand?.appName?.trim() || 'our marina';
|
||||
|
||||
// Fetch last 5 notes
|
||||
const recentNotes = await db
|
||||
.select({ content: interestNotes.content, createdAt: interestNotes.createdAt })
|
||||
@@ -117,6 +123,7 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
|
||||
context,
|
||||
berthMooring,
|
||||
pipelineStage: interest.pipelineStage,
|
||||
portName: brandingAppName,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,6 +260,7 @@ async function generateEmailDraft(payload: GenerateEmailDraftPayload): Promise<D
|
||||
context,
|
||||
berthMooring,
|
||||
pipelineStage: interest.pipelineStage,
|
||||
portName: brandingAppName,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,26 +274,28 @@ function buildTemplateDraft(opts: {
|
||||
context: string;
|
||||
berthMooring: string | null;
|
||||
pipelineStage: string;
|
||||
portName: string;
|
||||
}): DraftResult {
|
||||
const { clientName, context, berthMooring, pipelineStage } = opts;
|
||||
const { clientName, context, berthMooring, pipelineStage, portName } = opts;
|
||||
const berthText = berthMooring ? `berth ${berthMooring}` : 'your requested berth';
|
||||
const signoff = `Kind regards,\n${portName} Team`;
|
||||
|
||||
const templates: Record<string, { subject: string; body: string }> = {
|
||||
introduction: {
|
||||
subject: `Welcome to Port Nimara – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nThank you for your interest in Port Nimara. We are delighted to introduce our marina facilities and look forward to discussing how we can accommodate your needs for ${berthText}.\n\nPlease feel free to reach out at any time.\n\nKind regards,\nPort Nimara Team`,
|
||||
subject: `Welcome to ${portName} – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nThank you for your interest in ${portName}. We are delighted to introduce our marina facilities and look forward to discussing how we can accommodate your needs for ${berthText}.\n\nPlease feel free to reach out at any time.\n\n${signoff}`,
|
||||
},
|
||||
follow_up: {
|
||||
subject: `Following up – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nI wanted to follow up regarding your interest in ${berthText}. Please let us know if you have any questions or if there is anything we can assist you with.\n\nWe look forward to hearing from you.\n\nKind regards,\nPort Nimara Team`,
|
||||
body: `Dear ${clientName},\n\nI wanted to follow up regarding your interest in ${berthText}. Please let us know if you have any questions or if there is anything we can assist you with.\n\nWe look forward to hearing from you.\n\n${signoff}`,
|
||||
},
|
||||
stage_update: {
|
||||
subject: `Update on your application – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nWe are pleased to inform you that your application for ${berthText} has progressed to the "${stageLabel(pipelineStage)}" stage.\n\nWe will be in touch shortly with the next steps.\n\nKind regards,\nPort Nimara Team`,
|
||||
body: `Dear ${clientName},\n\nWe are pleased to inform you that your application for ${berthText} has progressed to the "${stageLabel(pipelineStage)}" stage.\n\nWe will be in touch shortly with the next steps.\n\n${signoff}`,
|
||||
},
|
||||
general: {
|
||||
subject: `Message from Port Nimara – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nThank you for your continued interest in Port Nimara. We appreciate your patience and look forward to assisting you with ${berthText}.\n\nKind regards,\nPort Nimara Team`,
|
||||
subject: `Message from ${portName} – ${clientName}`,
|
||||
body: `Dear ${clientName},\n\nThank you for your continued interest in ${portName}. We appreciate your patience and look forward to assisting you with ${berthText}.\n\n${signoff}`,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const emailWorker = new Worker(
|
||||
portId,
|
||||
fallback: email.subject,
|
||||
tokens: {
|
||||
portName: portName ?? 'Port Nimara',
|
||||
portName: portName ?? 'the marina',
|
||||
recipientName: firstName,
|
||||
mooringNumber: mooringNumber ?? '',
|
||||
},
|
||||
@@ -83,7 +83,7 @@ export const emailWorker = new Worker(
|
||||
portId,
|
||||
fallback: notification.subject,
|
||||
tokens: {
|
||||
portName: portName ?? 'Port Nimara',
|
||||
portName: portName ?? 'the marina',
|
||||
clientName: fullName,
|
||||
mooringNumber: mooringNumber ?? '',
|
||||
email,
|
||||
|
||||
@@ -83,9 +83,18 @@ export const notificationsWorker = new Worker(
|
||||
const linkHtml = notif.link
|
||||
? `<p><a href="${safeUrl(`${process.env.APP_URL ?? ''}${notif.link}`)}">View in CRM</a></p>`
|
||||
: '';
|
||||
|
||||
// Subject prefix = port branding `appName` so multi-tenant
|
||||
// deploys read "[Port Amador]"/"[Other Marina]" instead of
|
||||
// a hardcoded "[Port Nimara]".
|
||||
const { getPortBrandingConfig } = await import('@/lib/services/port-config');
|
||||
const portBrand = notif.portId
|
||||
? await getPortBrandingConfig(notif.portId).catch(() => null)
|
||||
: null;
|
||||
const prefix = portBrand?.appName?.trim() || 'CRM';
|
||||
await sendEmail(
|
||||
authUser.email,
|
||||
`[Port Nimara] ${notif.title}`,
|
||||
`[${prefix}] ${notif.title}`,
|
||||
`<p>${bodyText}</p>${linkHtml}`,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user