feat(portal): branded auth pages + legacy email styling + dev redirect override

- New PortalAuthShell component: blurred Port Nimara overhead background +
  circular logo + white rounded card, used by /portal/login,
  /portal/activate, /portal/reset-password
- New email/templates/portal-auth.ts: table-based, responsive (max-width
  600px / width 100%), matching the existing legacy inquiry templates;
  replaces the inline templates that lived in portal-auth.service
- EMAIL_REDIRECT_TO env override: when set, sendEmail routes every
  outbound message to that address regardless of recipient and tags the
  subject with "[redirected from <original>]". Dev/test safety net only;
  unset in production
- Portal password minimum length 12 → 9 (service + both API routes +
  client-side form)
- Dev helper script scripts/dev-trigger-portal-invite.ts: seeds a portal
  user against the first port-nimara client and uses EMAIL_REDIRECT_TO
  as the stored email so the tester can sign in with the address that
  received the activation mail

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-04-27 15:04:21 +02:00
parent c4085265ff
commit 4441f1177f
10 changed files with 396 additions and 201 deletions

View File

@@ -45,15 +45,24 @@ export async function sendEmail(
): Promise<nodemailer.SentMessageInfo> {
const transporter = createTransporter();
const requestedTo = Array.isArray(to) ? to.join(', ') : to;
const effectiveTo = env.EMAIL_REDIRECT_TO ?? requestedTo;
const effectiveSubject = env.EMAIL_REDIRECT_TO
? `[redirected from ${requestedTo}] ${subject}`
: subject;
const info = await transporter.sendMail({
from: from ?? env.SMTP_FROM ?? `Port Nimara CRM <noreply@${env.SMTP_HOST}>`,
to: Array.isArray(to) ? to.join(', ') : to,
subject,
to: effectiveTo,
subject: effectiveSubject,
html,
...(text ? { text } : {}),
});
logger.debug({ messageId: info.messageId, to, subject }, 'Email sent');
logger.debug(
{ messageId: info.messageId, to: effectiveTo, originalTo: requestedTo, subject },
env.EMAIL_REDIRECT_TO ? 'Email sent (redirected)' : 'Email sent',
);
return info;
}