diff --git a/src/app/(dashboard)/[portSlug]/admin/branding/page.tsx b/src/app/(dashboard)/[portSlug]/admin/branding/page.tsx index 7ef3d850..ff700a65 100644 --- a/src/app/(dashboard)/[portSlug]/admin/branding/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/branding/page.tsx @@ -45,6 +45,14 @@ const FIELDS: SettingFieldDef[] = [ imageAspect: 1, defaultValue: '', }, + { + key: 'branding_email_background_url', + label: 'Email background image', + description: + 'Optional blurred photo shown behind the white email card. Leave blank to use the built-in Port Nimara overhead. Recommended: 1920x1080 JPG, pre-blurred to ~20px gaussian so it reads as a soft background even on small clients.', + type: 'image-upload', + defaultValue: '', + }, { key: 'branding_primary_color', label: 'Primary color', diff --git a/src/lib/email/branding-resolver.ts b/src/lib/email/branding-resolver.ts index bb30a8fb..76831808 100644 --- a/src/lib/email/branding-resolver.ts +++ b/src/lib/email/branding-resolver.ts @@ -18,6 +18,7 @@ export async function getBrandingShell( const cfg = await getPortBrandingConfig(portId); return { logoUrl: cfg.logoUrl, + backgroundUrl: cfg.emailBackgroundUrl, primaryColor: cfg.primaryColor, emailHeaderHtml: cfg.emailHeaderHtml, emailFooterHtml: cfg.emailFooterHtml, diff --git a/src/lib/email/shell.ts b/src/lib/email/shell.ts index e44a33cf..c06a4a12 100644 --- a/src/lib/email/shell.ts +++ b/src/lib/email/shell.ts @@ -23,6 +23,10 @@ const DEFAULT_PRIMARY_COLOR = '#0F4C81'; export interface BrandingShell { logoUrl: string | null; + /** Phase 5: blurred page-background image rendered behind the white + * card. Defaults to the Port Nimara overhead image. Ports with + * their own marina photography override via system_settings. */ + backgroundUrl: string | null; primaryColor: string | null; emailHeaderHtml: string | null; emailFooterHtml: string | null; @@ -36,6 +40,7 @@ interface ShellOpts { export function renderShell({ title, body, branding }: ShellOpts): string { const logoUrl = branding?.logoUrl ?? DEFAULT_LOGO_URL; + const backgroundUrl = branding?.backgroundUrl ?? DEFAULT_BACKGROUND_URL; const headerHtml = branding?.emailHeaderHtml ?? ''; const footerHtml = branding?.emailFooterHtml ?? ''; @@ -52,7 +57,7 @@ export function renderShell({ title, body, branding }: ShellOpts): string { - +
diff --git a/src/lib/services/port-config.ts b/src/lib/services/port-config.ts index f964125b..65a98a69 100644 --- a/src/lib/services/port-config.ts +++ b/src/lib/services/port-config.ts @@ -99,6 +99,11 @@ export const SETTING_KEYS = { brandingAppName: 'branding_app_name', brandingEmailHeaderHtml: 'branding_email_header_html', brandingEmailFooterHtml: 'branding_email_footer_html', + // Phase 5: per-port background image (the blurred overhead photo + // shown behind the white card in every transactional email + the + // branded auth shell). Defaults to the Port Nimara overhead photo + // when blank. + brandingEmailBackgroundUrl: 'branding_email_background_url', // Reminders (port-level defaults) reminderDefaultDays: 'reminder_default_days', @@ -518,6 +523,7 @@ export async function listDocumensoWebhookSecrets(): Promise { - const [logoUrl, primaryColor, appName, emailHeaderHtml, emailFooterHtml] = await Promise.all([ - readSetting(SETTING_KEYS.brandingLogoUrl, portId), - readSetting(SETTING_KEYS.brandingPrimaryColor, portId), - readSetting(SETTING_KEYS.brandingAppName, portId), - readSetting(SETTING_KEYS.brandingEmailHeaderHtml, portId), - readSetting(SETTING_KEYS.brandingEmailFooterHtml, portId), - ]); + const [logoUrl, emailBackgroundUrl, primaryColor, appName, emailHeaderHtml, emailFooterHtml] = + await Promise.all([ + readSetting(SETTING_KEYS.brandingLogoUrl, portId), + readSetting(SETTING_KEYS.brandingEmailBackgroundUrl, portId), + readSetting(SETTING_KEYS.brandingPrimaryColor, portId), + readSetting(SETTING_KEYS.brandingAppName, portId), + readSetting(SETTING_KEYS.brandingEmailHeaderHtml, portId), + readSetting(SETTING_KEYS.brandingEmailFooterHtml, portId), + ]); return { logoUrl: logoUrl ?? DEFAULT_BRANDING.logoUrl, + emailBackgroundUrl: emailBackgroundUrl ?? DEFAULT_BRANDING.emailBackgroundUrl, primaryColor: primaryColor ?? DEFAULT_BRANDING.primaryColor, appName: appName ?? DEFAULT_BRANDING.appName, emailHeaderHtml: emailHeaderHtml ?? DEFAULT_BRANDING.emailHeaderHtml,