Initial commit

Next.js + Express event management app for Hope Family Church.
This commit is contained in:
2026-07-23 15:26:47 +02:00
commit 3d381944d2
246 changed files with 57565 additions and 0 deletions
@@ -0,0 +1,71 @@
"use client";
import React, { useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
type BannerType = "info" | "warning" | "success" | "danger";
interface Banner {
message: string;
type: BannerType;
liveFrom: string | null;
liveTill: string | null;
}
const STYLES: Record<BannerType, string> = {
info: "bg-blue-600 text-white",
warning: "bg-amber-400 text-amber-950",
success: "bg-emerald-600 text-white",
danger: "bg-red-600 text-white",
};
function dismissKey(banner: Banner) {
return `banner_dismissed_${banner.message}_${banner.liveFrom}_${banner.liveTill}`;
}
export function BannerBar() {
const [banner, setBanner] = useState<Banner | null>(null);
const [dismissed, setDismissed] = useState(false);
useEffect(() => {
apiFetch<Banner>("/api/banner")
.then(b => {
if (!b?.message) return;
setBanner(b);
const key = dismissKey(b);
if (typeof window !== "undefined" && localStorage.getItem(key) === "1") {
setDismissed(true);
}
})
.catch(() => {});
}, []);
if (!banner || !banner.message || dismissed) return null;
const now = Date.now();
const from = banner.liveFrom ? new Date(banner.liveFrom).getTime() : null;
const till = banner.liveTill ? new Date(banner.liveTill).getTime() : null;
if (from !== null && now < from) return null;
if (till !== null && now > till) return null;
const dismiss = () => {
if (typeof window !== "undefined") {
localStorage.setItem(dismissKey(banner), "1");
}
setDismissed(true);
};
return (
<div className={`w-full px-4 py-2.5 flex items-center justify-center gap-3 text-sm font-medium ${STYLES[banner.type ?? "info"]}`}>
<span className="flex-1 text-center">{banner.message}</span>
<button
onClick={dismiss}
aria-label="Dismiss banner"
className="shrink-0 opacity-70 hover:opacity-100 transition-opacity text-lg leading-none"
>
×
</button>
</div>
);
}