feat(post-audit): Phase 1.3 + 1.4 + Phase 2 signals + pulse admin
Phase 1.3 — signing-invitation role copy - Order-agnostic phrasing (was assuming client→developer→approver order; ports configure any sequence so the "client has already signed" assumption was brittle). - Explicit developer-role branch + safe default for unknown roles. Phase 1.4 — supplemental form per-port URL - New supplemental_form_url registry entry (email.from section). - Threaded through getPortEmailConfig → PortEmailConfig.supplementalFormUrl. - /api/v1/interests/[id]/supplemental-info-request resolves the link via per-port URL when set, falls back to /public/supplemental-info/<token> CRM route when blank. Phase 2 — deal-pulse signal expansion + admin config - Compute function gains: - +5 eoi_sent_recent (≤14d) — was previously invisible - +15 deposit_received — strongest near-commit signal - +10 contract_signed — closed-loop reinforcement until outcome flips - -25 document_declined — strongest cooling signal - -20 reservation_cancelled — booked-then-cancelled warning - -30 berth_sold_to_other — primary berth lost to another deal - Each signal honours optional per-port `signal_<id>_enabled` toggle. - Registry adds master toggle (pulse_enabled), per-signal toggles, and per-port label overrides (Hot/Warm/Cold rename). - New /admin/pulse page mounted via RegistryDrivenForm. - AdminSectionsBrowser entry under Configuration. Data-wiring for the 3 risk signals (declined/cancelled/sold-to-other) needs follow-up: requires either schema timestamps on interests or derivation from event tables. Master plan §B captures the gap. Tests: 1374/1374 passing. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,10 @@ export const SETTING_KEYS = {
|
||||
emailFromName: 'email_from_name',
|
||||
emailFromAddress: 'email_from_address',
|
||||
emailReplyTo: 'email_reply_to',
|
||||
// §1.4: optional per-port URL that the supplemental-info email links
|
||||
// to (typically the marketing site's hosted form). When blank, the
|
||||
// built-in CRM route `/public/supplemental-info/<token>` is used.
|
||||
supplementalFormUrl: 'supplemental_form_url',
|
||||
// email_signature_html / email_footer_html — removed; the email shell
|
||||
// reads branding_email_header_html / branding_email_footer_html from
|
||||
// /admin/branding, which is the source of truth.
|
||||
@@ -220,6 +224,12 @@ export interface PortEmailConfig {
|
||||
* account. Defaults to false for safety.
|
||||
*/
|
||||
allowPersonalAccountSends: boolean;
|
||||
/**
|
||||
* §1.4: optional per-port URL for the supplemental-info email link.
|
||||
* When set, the email contains `${supplementalFormUrl}?token=<raw>`;
|
||||
* when null, the built-in CRM route is used.
|
||||
*/
|
||||
supplementalFormUrl: string | null;
|
||||
}
|
||||
|
||||
export async function getPortEmailConfig(portId: string): Promise<PortEmailConfig> {
|
||||
@@ -232,6 +242,7 @@ export async function getPortEmailConfig(portId: string): Promise<PortEmailConfi
|
||||
smtpUser,
|
||||
smtpPass,
|
||||
allowPersonalAccountSends,
|
||||
supplementalFormUrl,
|
||||
] = await Promise.all([
|
||||
readSetting<string>(SETTING_KEYS.emailFromName, portId),
|
||||
readSetting<string>(SETTING_KEYS.emailFromAddress, portId),
|
||||
@@ -241,6 +252,7 @@ export async function getPortEmailConfig(portId: string): Promise<PortEmailConfi
|
||||
readSetting<string>(SETTING_KEYS.smtpUserOverride, portId),
|
||||
readSetting<string>(SETTING_KEYS.smtpPassOverride, portId),
|
||||
readSetting<boolean>(SETTING_KEYS.emailAllowPersonalAccountSends, portId),
|
||||
readSetting<string>(SETTING_KEYS.supplementalFormUrl, portId),
|
||||
]);
|
||||
|
||||
// Parse env.SMTP_FROM into name + address if no port override
|
||||
@@ -265,6 +277,7 @@ export async function getPortEmailConfig(portId: string): Promise<PortEmailConfi
|
||||
smtpUser: smtpUser ?? env.SMTP_USER ?? null,
|
||||
smtpPass: smtpPass ?? env.SMTP_PASS ?? null,
|
||||
allowPersonalAccountSends: allowPersonalAccountSends ?? false,
|
||||
supplementalFormUrl: supplementalFormUrl ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user