import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { AnchoredBottomBannerProps } from "./types"; import Link from "next/link"; import { MaterialIcon } from "@shared/components/material-icon"; function parseCountdownDateTime(value?: string): number | undefined { if (!value) return undefined; const parsed = Date.parse(value); if (!Number.isFinite(parsed)) { console.error("Invalid countdown datetime", { value }); return undefined; } return parsed; } function formatCountdown(totalSeconds: number) { const safeSeconds = Math.max(0, Math.floor(totalSeconds)); const hours = Math.floor(safeSeconds / 3600); const minutes = Math.floor((safeSeconds % 3600) / 60); const seconds = safeSeconds % 60; return `${String(hours).padStart(2, "0")}H : ${String(minutes).padStart(2, "0")}M : ${String(seconds).padStart(2, "0")}`; } export const AnchoredBottomBanner: React.FC = ({ ctaSuffixText, backgroundColor, iconName, boxShadow, ctaButtonLabel, ctaButtonLink, ctaButtonTarget, anchorId = "anchored-banner", enableCountdownTimer, countdownStartDateTime, countdownEndDateTime, }) => { const backGroundColorClasses = { navy: "bg-bg-fill-inverse", green: "bg-bg-fill-success", blue: "bg-bg-fill-brand-supporting", purple: "bg-bg-fill-brand-tertiary", yellow: "bg-bg-fill-brand-accent", white: "bg-white", }; const bgClass = backgroundColor ? backGroundColorClasses[backgroundColor] : "bg-bg-fill-brand-accent"; const isLightBackground = backgroundColor === "yellow" || backgroundColor === "white" || !backgroundColor; const textColorClass = isLightBackground ? "text-text-primary" : "text-white"; // Memoize parsed timestamps so they aren't re-parsed every second const endMs = useMemo( () => parseCountdownDateTime(countdownEndDateTime), [countdownEndDateTime] ); const startMs = useMemo( () => parseCountdownDateTime(countdownStartDateTime), [countdownStartDateTime] ); const isTimerValid = useMemo(() => { if (!enableCountdownTimer || endMs === undefined) return false; if (countdownStartDateTime && startMs === undefined) return false; if (startMs !== undefined && startMs >= endMs) { console.error("Invalid countdown range: start must be before end", { countdownStartDateTime, countdownEndDateTime, }); return false; } return true; }, [ enableCountdownTimer, endMs, startMs, countdownStartDateTime, countdownEndDateTime, ]); const [nowMs, setNowMs] = useState(() => Date.now()); const intervalRef = useRef(null); const clearTimer = useCallback(() => { if (intervalRef.current !== null) { window.clearInterval(intervalRef.current); intervalRef.current = null; } }, []); useEffect(() => { if (!isTimerValid) { clearTimer(); return; } intervalRef.current = window.setInterval(() => { const now = Date.now(); // Auto-clear interval once countdown expires if (endMs !== undefined && now >= endMs) { clearTimer(); } setNowMs(now); }, 1000); return clearTimer; }, [isTimerValid, endMs, clearTimer]); const countdown = useMemo(() => { if (!isTimerValid || endMs === undefined) { return { shouldShow: false, text: "" }; } const isBeforeStart = startMs !== undefined && nowMs < startMs; const isAfterEnd = nowMs >= endMs; if (isBeforeStart || isAfterEnd) return { shouldShow: false, text: "" }; const remainingSeconds = (endMs - nowMs) / 1000; return { shouldShow: remainingSeconds > 0, text: formatCountdown(remainingSeconds), }; }, [isTimerValid, endMs, startMs, nowMs]); return (
{iconName && ( ["name"] } size={24} fill={1} className={`${textColorClass} align-text-bottom`} /> )} {countdown.shouldShow && ( {countdown.text} )} {countdown.shouldShow ? " " : null} {ctaButtonLabel && ctaButtonLabel}{" "} {ctaSuffixText && {ctaSuffixText}}
); }; export default AnchoredBottomBanner;