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:
2026-08-20 14:49:51 +02:00
co-authored by Claude Sonnet 5
parent 2d99b7cafe
commit 86af7093ac
33 changed files with 1174 additions and 198 deletions
+46 -10
View File
@@ -3,10 +3,13 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { AuthProvider } from "@/contexts/AuthContext";
import { SiteSettingsProvider } from "@/contexts/SiteSettingsContext";
import type { SiteSettings } from "@/contexts/SiteSettingsContext";
import { ToastProvider } from "@/components/shared/ToastProvider";
import { BannerBar } from "@/components/shared/BannerBar";
import { SetupGuard } from "@/components/shared/SetupGuard";
import HelpFab from "@/components/shared/HelpFab";
import { API_BASE, resolveToApiOrigin } from "@/lib/api";
import { buildThemeCssVars } from "@/lib/colorScale";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -18,21 +21,54 @@ const geistMono = Geist_Mono({
subsets: ["latin"],
});
const appName = process.env.NEXT_PUBLIC_APP_NAME || 'Hope Events';
const appName = process.env.NEXT_PUBLIC_APP_NAME || 'Cross Code';
export const metadata: Metadata = {
title: appName,
description: `Manage and register for events with ${appName}`,
icons: {
icon: "/favicon.ico",
},
};
/**
* Server-side settings fetch, shared by generateMetadata() and RootLayout()
* below. Both call this with the identical URL + options so Next's per-request
* fetch memoization dedupes them into a single network call. Degrades to `{}`
* (static defaults everywhere downstream) if the backend is unreachable at
* request time — intentional, not a bug: branding just isn't critical enough
* to fail the whole page render over.
*/
async function getServerSettings(): Promise<SiteSettings> {
try {
const res = await fetch(`${API_BASE}/api/settings`, { next: { revalidate: 60 } });
if (!res.ok) return {};
return await res.json();
} catch {
return {};
}
}
export async function generateMetadata(): Promise<Metadata> {
const settings = await getServerSettings();
const faviconUrl = settings.favicon_url ? resolveToApiOrigin(settings.favicon_url) : null;
return {
title: appName,
description: `Manage and register for events with ${appName}`,
icons: {
icon: faviconUrl || "/favicon.ico",
},
};
}
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const settings = await getServerSettings();
const cssVars = buildThemeCssVars({
primary: settings.primary_color || settings.accent_color,
secondary: settings.secondary_color,
accent: settings.primary_color ? settings.accent_color : undefined,
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SiteSettingsProvider>
{cssVars && (
<style id="brand-theme" dangerouslySetInnerHTML={{ __html: `:root{${cssVars}}` }} />
)}
<SiteSettingsProvider initialSettings={settings}>
<AuthProvider>
<ToastProvider>
<SetupGuard />