71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
"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>
|
||
);
|
||
} |