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>
76 lines
2.2 KiB
TypeScript
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);
|
|
} |