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>
133 lines
5.4 KiB
TypeScript
133 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
import { usePathname } from "next/navigation";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { useSiteSettings } from "@/contexts/SiteSettingsContext";
|
|
import { BottomNav } from "./BottomNav";
|
|
import churchLogo from "@/app/church_logo.jpg";
|
|
import { appName } from "@/lib/siteConfig";
|
|
import { resolveToApiOrigin } from "@/lib/api";
|
|
|
|
type NavLink = {
|
|
href: string;
|
|
label: string;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
export const Navbar = () => {
|
|
const { user, loading: authLoading, logout } = useAuth();
|
|
const { settings, loading: settingsLoading } = useSiteSettings();
|
|
const pathname = usePathname();
|
|
const displayName = settings.org_name || appName;
|
|
const logoSrc = settings.logo_url ? resolveToApiOrigin(settings.logo_url) : null;
|
|
|
|
const isActive = (href: string) => {
|
|
if (href.includes("#")) return false;
|
|
if (href === "/") return pathname === "/";
|
|
return pathname?.startsWith(href) ?? false;
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
logout();
|
|
window.location.href = "/";
|
|
};
|
|
|
|
const navLinks: NavLink[] = [
|
|
{ href: "/", label: "Home" },
|
|
{ href: "/events", label: "Events" },
|
|
{ href: "/contact", label: "Contact" },
|
|
];
|
|
|
|
const authLinks: NavLink[] = user
|
|
? [
|
|
{ href: "/dashboard", label: "Dashboard" },
|
|
{
|
|
href: "#",
|
|
label: "Logout",
|
|
onClick: handleLogout,
|
|
},
|
|
]
|
|
: [
|
|
{ href: "/login", label: "Login" },
|
|
{ href: "/register", label: "Register" },
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<header className="bg-white shadow-md sticky top-0 z-50">
|
|
<nav className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
|
|
<Link href="/" className="flex items-center gap-2">
|
|
{settingsLoading ? (
|
|
<div className="h-10 w-10 rounded-sm bg-gray-100 animate-pulse" />
|
|
) : logoSrc ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img src={logoSrc} alt="Logo" className="h-10 w-auto object-contain rounded-sm" />
|
|
) : (
|
|
<Image
|
|
src={churchLogo}
|
|
alt="Church logo"
|
|
width={50}
|
|
height={50}
|
|
className="rounded-sm object-contain"
|
|
priority
|
|
/>
|
|
)}
|
|
{!settingsLoading && (
|
|
<span className="text-xl font-bold text-brand-600">{displayName}</span>
|
|
)}
|
|
</Link>
|
|
|
|
{/* Desktop links */}
|
|
<div className="hidden md:flex items-center space-x-6">
|
|
{navLinks.map(link => (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className={
|
|
"text-sm pb-0.5 border-b-2 transition-colors " +
|
|
(isActive(link.href)
|
|
? "text-brand-600 border-brand-600 font-medium"
|
|
: "text-gray-700 border-transparent hover:text-brand-600")
|
|
}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
|
|
{/* Auth links are only known once the token has been validated —
|
|
rendering nothing here (instead of guessing "logged out") avoids
|
|
a Login/Register -> Dashboard/Logout flash on every load. */}
|
|
{!authLoading && authLinks.map(link =>
|
|
link.onClick ? (
|
|
<button
|
|
key={link.label}
|
|
onClick={link.onClick}
|
|
className="text-sm pb-0.5 border-b-2 border-transparent text-gray-700 hover:text-brand-600 transition-colors"
|
|
>
|
|
{link.label}
|
|
</button>
|
|
) : (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className={
|
|
"text-sm pb-0.5 border-b-2 transition-colors " +
|
|
(isActive(link.href)
|
|
? "text-brand-600 border-brand-600 font-medium"
|
|
: "text-gray-700 border-transparent hover:text-brand-600")
|
|
}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
)
|
|
)}
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
<BottomNav />
|
|
</>
|
|
);
|
|
};
|