feat(uat-batch): Groups F + G + H — DocsHub/signing + admin consolidation + email
F27–F29, G30, G31, H32, H33 from the 2026-05-21 plan.
Shipped now:
F28 Past-milestones expandable history. The Past strip on the
Interest overview becomes an <Accordion> — each row collapses
to the same one-line summary as before, expands to render the
full <MilestoneSection> (steps list, sub-status, inline doc
actions). Reuses the existing MilestoneSection so no new
per-milestone rendering needs to be maintained.
F29 Watchers configurable at document creation time. The unified
create-document wizard gets a Watchers section with a
multi-select checkbox list backed by /api/v1/admin/users/picker.
Selected user ids are sent in the `watchers` array on the POST
(replacing the prior hardcoded `[]`). UI matches the
post-creation WatchersCard so reps see the same identity rows
regardless of entry point.
G30 /admin/invitations merged into /admin/users. The Users page
now wraps the existing UserList + InvitationsManager in a
Tabs control (Active users / Invitations). The standalone
/admin/invitations route returns a redirect to the merged page
for bookmark back-compat. Removed nav catalog entry +
admin-sections-browser tile; extended the Users catalog
keywords with "invitations / pending invites / onboarding"
so command-K search still lands on the right surface.
G31 /admin/ai picks up the berth-PDF-parser section + a "planned
AI surfaces" placeholder. Berth PDF parser remains
env-configured today; the page now documents it so admins
don't hunt for the controls. Closes the "where do I configure
AI?" loop.
H32 Email settings explainer panel above the SMTP cards. Spells
out why noreply + sales have separate credentials and which
workflows ship from each mailbox. Existing field titles
gained the "(noreply)" suffix so the model maps cleanly.
H33 Supplemental-info-request email rebuilt to use the shared
branded shell (logo + blurred overhead background + max-
width 600 table layout) instead of the prior plain-HTML
page. Per-port branding (logo / primary color / background /
header / footer) flows from getPortBrandingConfig. CTA
button picks up the port's primary color.
Already shipped (verified pre-shipped):
F27 DocumentsHub root view already hides the breadcrumb via
`selectedFolderId !== undefined` conditional.
Verified: tsc clean, vitest 1454/1454.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@ import {
|
||||
} from '@/lib/services/supplemental-forms.service';
|
||||
import { sendEmail } from '@/lib/email';
|
||||
import { env } from '@/lib/env';
|
||||
import { getPortEmailConfig } from '@/lib/services/port-config';
|
||||
import { getPortBrandingConfig, getPortEmailConfig } from '@/lib/services/port-config';
|
||||
import { brandingPrimaryColor, renderShell } from '@/lib/email/shell';
|
||||
|
||||
/**
|
||||
* POST /api/v1/interests/[id]/supplemental-info-request
|
||||
@@ -82,22 +83,56 @@ export const POST = withAuth(
|
||||
// `sendEmail` body flag.
|
||||
const willSendEmail = resendTokenId ? true : shouldSendEmail;
|
||||
if (willSendEmail && result.clientEmail) {
|
||||
const html = `
|
||||
<p>Hello ${escapeHtml(result.clientName)},</p>
|
||||
<p>Before we draft your Expression of Interest, we need to confirm a few details.
|
||||
// Render through the shared branded shell (logo + blurred overhead
|
||||
// background + max-width 600 table layout) so the supplemental-
|
||||
// info email matches portal-activation / reset / login + the rest
|
||||
// of the branded surfaces. Per-port branding (logo, primary
|
||||
// color, background image, header/footer) flows from
|
||||
// system_settings via getPortBrandingConfig.
|
||||
const branding = await getPortBrandingConfig(ctx.portId);
|
||||
const accent = brandingPrimaryColor({
|
||||
logoUrl: branding.logoUrl,
|
||||
backgroundUrl: branding.emailBackgroundUrl,
|
||||
primaryColor: branding.primaryColor,
|
||||
emailHeaderHtml: branding.emailHeaderHtml,
|
||||
emailFooterHtml: branding.emailFooterHtml,
|
||||
});
|
||||
const body = `
|
||||
<h1 style="font-family:Arial,sans-serif;font-size:20px;font-weight:600;color:#0f172a;margin:0 0 16px;">
|
||||
One quick step before your EOI
|
||||
</h1>
|
||||
<p style="font-family:Arial,sans-serif;font-size:14px;line-height:1.55;color:#334155;margin:0 0 12px;">
|
||||
Hello ${escapeHtml(result.clientName)},
|
||||
</p>
|
||||
<p style="font-family:Arial,sans-serif;font-size:14px;line-height:1.55;color:#334155;margin:0 0 16px;">
|
||||
Before we draft your Expression of Interest, we need to confirm a few details.
|
||||
The form below is pre-filled with what we have on file — please review, correct
|
||||
anything that's wrong, and add what's missing.</p>
|
||||
<p style="text-align:center;margin:24px 0">
|
||||
anything that's wrong, and add what's missing.
|
||||
</p>
|
||||
<p style="text-align:center;margin:24px 0;">
|
||||
<a href="${link}"
|
||||
style="background:#1e3a8a;color:#fff;text-decoration:none;padding:12px 24px;border-radius:6px;display:inline-block">
|
||||
style="display:inline-block;background:${accent};color:#ffffff;text-decoration:none;padding:12px 24px;border-radius:6px;font-family:Arial,sans-serif;font-size:14px;font-weight:600;">
|
||||
Open the form
|
||||
</a>
|
||||
</p>
|
||||
<p style="color:#64748b;font-size:12px">
|
||||
<p style="font-family:Arial,sans-serif;font-size:12px;color:#64748b;margin:0 0 4px;">
|
||||
This link expires on ${result.expiresAt.toUTCString()}.
|
||||
If you didn't expect this email, please let us know.
|
||||
</p>
|
||||
<p style="font-family:Arial,sans-serif;font-size:12px;color:#64748b;margin:0;">
|
||||
If you didn't expect this email, please let us know.
|
||||
</p>
|
||||
`;
|
||||
const html = renderShell({
|
||||
title: 'Please complete a few details before we draft your EOI',
|
||||
body,
|
||||
branding: {
|
||||
logoUrl: branding.logoUrl,
|
||||
backgroundUrl: branding.emailBackgroundUrl,
|
||||
primaryColor: branding.primaryColor,
|
||||
emailHeaderHtml: branding.emailHeaderHtml,
|
||||
emailFooterHtml: branding.emailFooterHtml,
|
||||
},
|
||||
});
|
||||
await sendEmail(
|
||||
result.clientEmail,
|
||||
'Please complete a few details before we draft your EOI',
|
||||
|
||||
Reference in New Issue
Block a user