Add admin-configurable branding (colors, logo, favicon) and generic default fallbacks
Site Settings -> Branding now supports a Primary/Secondary/Accent brand color system applied site-wide (buttons, nav, hover states, links) and to outgoing email header/CTA colors, plus a favicon upload alongside the existing logo upload, a live preview panel (website/email x desktop/mobile), and logo-based color suggestions. The setup wizard's Branding step got the same treatment. Fixes two related bugs found along the way: the setup wizard's logo/favicon upload was missing its auth token, and a static favicon.ico in Next's special app/ convention path was silently overriding the dynamic one. Also replaces every "Hope Events"/"Hope Family Church" default (org name, email subjects, WhatsApp messages, report metadata, API docs) with a neutral "Cross Code" placeholder, and the optional legal settings (operator name, IO details, website URL, effective date) with obviously-generic placeholders instead of defaulting to real personal/organisational details -- since this platform is deployed for multiple organisations. Adds SETTINGS.md documenting every setting's default behaviour. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ async function sendMail({ to, subject, html, text, attachments }) {
|
||||
return;
|
||||
}
|
||||
const { transporter, cfg } = _getTransporter();
|
||||
const from = cfg.from || 'no-reply@hope-events.local';
|
||||
const from = cfg.from || 'no-reply@crosscode.local';
|
||||
const info = await transporter.sendMail({ from, to, subject, html, text, ...(attachments ? { attachments } : {}) });
|
||||
|
||||
if (transporter.options && transporter.options.jsonTransport) {
|
||||
@@ -74,12 +74,17 @@ async function sendMail({ to, subject, html, text, attachments }) {
|
||||
|
||||
function getOrg() {
|
||||
const urlFallback = process.env.APP_BASE_URL || process.env.FRONTEND_URL || 'http://localhost:3001';
|
||||
// headerColor prefers the new primary_color setting; falls back to the
|
||||
// legacy accent_color value (which used to double as "the one brand color"
|
||||
// before the 3-color Primary/Secondary/Accent system existed) so emails
|
||||
// stay branded correctly until the admin re-saves the Branding tab.
|
||||
const primary = getSettingSync('primary_color', '') || getSettingSync('accent_color', '');
|
||||
return {
|
||||
name: getSettingSync('org_name', process.env.ORG_NAME || 'Hope Events'),
|
||||
name: getSettingSync('org_name', process.env.ORG_NAME || 'Cross Code'),
|
||||
tagline: getSettingSync('org_tagline', process.env.ORG_TAGLINE || 'Connecting community through events'),
|
||||
email: getSettingSync('smtp_from', process.env.EMAIL_FROM || process.env.EMAIL_USER || ''),
|
||||
url: getSettingSync('app_base_url', urlFallback).replace(/\/$/, ''),
|
||||
headerColor: getSettingSync('accent_color', process.env.EMAIL_HEADER_COLOR || '#1e3a5f'),
|
||||
headerColor: primary || process.env.EMAIL_HEADER_COLOR || '#1e3a5f',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,8 +142,9 @@ ${preheader ? `<div style="display:none;font-size:1px;line-height:1px;max-height
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/** Renders a prominent CTA button. */
|
||||
function ctaButton(label, url, { bg = '#2563eb', fg = '#ffffff' } = {}) {
|
||||
/** Renders a prominent CTA button. Defaults to the org's brand color unless a semantic override (e.g. green for "activate", dark neutral for "log in") is passed explicitly. */
|
||||
function ctaButton(label, url, { bg, fg = '#ffffff' } = {}) {
|
||||
bg = bg || getOrg().headerColor;
|
||||
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:28px auto 8px auto">
|
||||
<tr><td align="center" style="border-radius:8px;background-color:${bg};mso-padding-alt:0px">
|
||||
<a href="${url}" target="_blank"
|
||||
@@ -150,8 +156,9 @@ function ctaButton(label, url, { bg = '#2563eb', fg = '#ffffff' } = {}) {
|
||||
|
||||
/** Renders a fallback link below a CTA button. */
|
||||
function fallbackLink(url) {
|
||||
const color = getOrg().headerColor;
|
||||
return `<p style="text-align:center;margin:4px 0 0 0;font-size:12px;color:#94a3b8;word-break:break-all">
|
||||
Or copy this link: <a href="${url}" style="color:#2563eb">${url}</a>
|
||||
Or copy this link: <a href="${url}" style="color:${color}">${url}</a>
|
||||
</p>`;
|
||||
}
|
||||
|
||||
@@ -160,7 +167,12 @@ function divider() {
|
||||
return `<div style="border-top:1px solid #f1f5f9;margin:32px 0"></div>`;
|
||||
}
|
||||
|
||||
/** Coloured callout box. type: info | success | warning | danger | neutral */
|
||||
/**
|
||||
* Coloured callout box. type: info | success | warning | danger | neutral
|
||||
* These 4 semantic colors are deliberately NOT brand-driven — a "this wasn't
|
||||
* you" security warning must always read as urgent/red regardless of the
|
||||
* org's brand color, so don't wire these to getOrg().headerColor.
|
||||
*/
|
||||
function callout(content, type = 'info') {
|
||||
const map = {
|
||||
info: { bg: '#eff6ff', border: '#3b82f6', color: '#1e40af' },
|
||||
@@ -177,9 +189,10 @@ function callout(content, type = 'info') {
|
||||
|
||||
/** Numbered payment option row. */
|
||||
function paymentOption(num, title, detail) {
|
||||
const bg = getOrg().headerColor;
|
||||
return `<tr>
|
||||
<td style="padding:14px 16px 14px 0;vertical-align:top;width:28px">
|
||||
<div style="width:26px;height:26px;border-radius:50%;background:#2563eb;color:#fff;font-size:13px;font-weight:700;text-align:center;line-height:26px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">${num}</div>
|
||||
<div style="width:26px;height:26px;border-radius:50%;background:${bg};color:#fff;font-size:13px;font-weight:700;text-align:center;line-height:26px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">${num}</div>
|
||||
</td>
|
||||
<td style="padding:14px 0;border-bottom:1px solid #f1f5f9;vertical-align:top">
|
||||
<p style="margin:0 0 4px 0;font-size:14px;font-weight:700;color:#1e293b;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif">${title}</p>
|
||||
|
||||
Reference in New Issue
Block a user