video-flow-b/components/HomeBanner.tsx
2025-10-17 15:43:40 +08:00

160 lines
5.0 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { fetchSettingByCode } from "@/api/serversetting";
export const HOME_BANNER_CODE = "homeBanner";
/** CTA config for banner */
export interface HomeBannerCTAConfig {
label?: string;
href?: string;
[key: string]: unknown;
}
/** Configuration payload for `HomeBanner` */
export interface HomeBannerConfig {
eyebrow?: string;
title?: string;
subtitle?: string;
description?: string;
backgroundImage?: string;
cta?: HomeBannerCTAConfig;
ctaText?: string;
ctaLink?: string;
[key: string]: unknown;
}
/**
* Renders the home banner with optional background image and CTA.
* Pulls configuration from server settings keyed by `HOME_BANNER_CODE`.
* @returns React.ReactElement | null
*/
export default function HomeBanner() {
const [config, setConfig] = useState<HomeBannerConfig | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let active = true;
async function load() {
setLoading(true);
setError(null);
try {
const value = await fetchSettingByCode<HomeBannerConfig>(HOME_BANNER_CODE);
if (!active) return;
setConfig(value ?? null);
} catch (err) {
if (!active) return;
const message = err instanceof Error ? err.message : "Failed to load home banner";
setError(message);
setConfig(null);
} finally {
if (active) {
setLoading(false);
}
}
}
load();
return () => {
active = false;
};
}, []);
if (loading || error || !config) {
// Render nothing until configuration schema is finalized.
return null;
}
const title = typeof config.title === "string" ? config.title : undefined;
const subtitle = typeof config.subtitle === "string" ? config.subtitle : undefined;
const description = typeof config.description === "string" ? config.description : undefined;
const eyebrow = typeof config.eyebrow === "string" ? config.eyebrow : undefined;
const backgroundImage = typeof config.backgroundImage === "string" ? config.backgroundImage : undefined;
let ctaLabel: string | undefined;
let ctaHref: string | undefined;
if (config.cta && typeof config.cta === "object") {
const { label, href } = config.cta as Record<string, unknown>;
if (typeof label === "string") ctaLabel = label;
if (typeof href === "string") ctaHref = href;
}
// Support legacy field names until the API contract is confirmed.
if (!ctaLabel && typeof config.ctaText === "string") {
ctaLabel = config.ctaText;
}
if (!ctaHref && typeof config.ctaLink === "string") {
ctaHref = config.ctaLink;
}
const hasContent = Boolean(title || subtitle || description || ctaLabel || backgroundImage);
if (!hasContent) {
return null;
}
return (
<section
data-alt="home-banner"
className="relative isolate overflow-hidden rounded-3xl px-6 py-16 text-white border-2 border-transparent hover:border-custom-blue/50 transition-all duration-300"
aria-label="Home banner"
>
{backgroundImage ? (
<img
data-alt="background-image"
src={backgroundImage}
className="absolute inset-0 h-full w-full object-cover"
/>
) : null}
{backgroundImage ? (
<div data-alt="background-overlay" className="absolute inset-0 bg-black/40" />
) : null}
<div
data-alt="banner-content"
className="relative flex max-w-4xl flex-col items-center gap-4 text-center md:items-start md:text-left"
>
{eyebrow ? (
<span data-alt="banner-eyebrow" className="text-xs font-semibold uppercase tracking-[0.3em] text-white/70">
{eyebrow}
</span>
) : null}
{title ? (
<h1 data-alt="banner-title" className="text-3xl font-semibold leading-tight md:text-5xl">
{title}
</h1>
) : null}
{subtitle ? (
<p data-alt="banner-subtitle" className="text-lg text-white/80 md:text-xl">{subtitle}</p>
) : null}
{description ? (
<p data-alt="banner-description" className="max-w-2xl text-base text-white/70">{description}</p>
) : null}
{ctaLabel ? (
ctaHref ? (
<a
data-alt="cta-link"
href={ctaHref}
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 px-6 py-2 text-sm font-medium text-white transition hover:border-white hover:bg-white hover:text-slate-900"
>
{ctaLabel}
</a>
) : (
<button
data-alt="cta-button"
type="button"
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 px-6 py-2 text-sm font-medium text-white transition hover:border-white hover:bg-white hover:text-slate-900"
>
{ctaLabel}
</button>
)
) : null}
</div>
</section>
);
}