Files
hope-events/frontend/src/contexts/SiteSettingsContext.tsx
T
joshuaandClaude Sonnet 5 86af7093ac 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>
2026-08-20 14:49:51 +02:00

76 lines
2.2 KiB
TypeScript

"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
export type SiteSettings = {
org_name?: string;
org_tagline?: string;
org_email?: string;
org_phone?: string;
org_address?: string;
// Branding — 3-color system. accent_color is the tertiary/highlight color;
// primary_color falls back to accent_color's legacy value if unset (see
// settingsController.js PUBLIC_KEYS comment).
primary_color?: string;
secondary_color?: string;
accent_color?: string;
logo_url?: string;
favicon_url?: string;
setup_complete?: string;
// Legal pages
legal_operator_name?: string;
legal_io_name?: string;
legal_io_email?: string;
legal_website_url?: string;
legal_effective_date?: string;
};
type SiteSettingsContextValue = {
settings: SiteSettings;
loading: boolean;
reload: () => void;
};
const SiteSettingsContext = createContext<SiteSettingsContextValue>({
settings: {},
loading: true,
reload: () => {},
});
export function SiteSettingsProvider({
children, initialSettings,
}: {
children: React.ReactNode;
/** Server-fetched settings from layout.tsx's generateMetadata/RootLayout — seeds state so there's no loading flash for anything reading useSiteSettings() (e.g. the navbar logo). */
initialSettings?: SiteSettings;
}) {
const [settings, setSettings] = useState<SiteSettings>(initialSettings || {});
const [loading, setLoading] = useState(!initialSettings);
const load = async () => {
try {
const data = await apiFetch<SiteSettings>("/api/settings");
setSettings(data || {});
} catch {
// Non-fatal — use defaults
} finally {
setLoading(false);
}
};
// Always refreshes in the background (cheap, 60s-cached server-side) even
// when seeded from initialSettings — self-heals if the SSR fetch in
// layout.tsx failed, and picks up any change since that request.
useEffect(() => { load(); }, []);
return (
<SiteSettingsContext.Provider value={{ settings, loading, reload: load }}>
{children}
</SiteSettingsContext.Provider>
);
}
export function useSiteSettings() {
return useContext(SiteSettingsContext);
}