diff --git a/components/layout/AnnouncementBanner.tsx b/components/layout/AnnouncementBanner.tsx index 29b7a02..2e3c880 100644 --- a/components/layout/AnnouncementBanner.tsx +++ b/components/layout/AnnouncementBanner.tsx @@ -4,7 +4,30 @@ import { useEffect, useState } from 'react' import Link from 'next/link' import { activeAnnouncement } from '@/data/announcements' -type WrapperProps = { children: React.ReactNode; className?: string } +type BannerLinkProps = { + href: string + external?: boolean + children: React.ReactNode + className?: string +} + +/** Declared at module scope — defining this inside the render remounted the + * banner contents on every render and reset their state. */ +function BannerLink({ href, external, children, className }: BannerLinkProps) { + if (external) { + return ( + + {children} + + ) + } + + return ( + + {children} + + ) +} export default function AnnouncementBanner() { const [visible, setVisible] = useState(false) @@ -20,6 +43,9 @@ export default function AnnouncementBanner() { // Check dismiss state if (localStorage.getItem(`banner_dismissed_${id}`)) return + // localStorage does not exist during SSR, so dismissal can only be resolved + // after mount. The banner starts hidden and reveals itself here. + // eslint-disable-next-line react-hooks/set-state-in-effect setVisible(true) }, []) @@ -32,25 +58,17 @@ export default function AnnouncementBanner() { setVisible(false) } - const LinkOrA = external - ? ({ children, className }: WrapperProps) => ( - - {children} - - ) - : ({ children, className }: WrapperProps) => ( - - {children} - - ) - return (
{/* Left: badge + message (clickable) */} - + {/* Badge */} {label} @@ -64,13 +82,17 @@ export default function AnnouncementBanner() { - + {/* Right: "View Event →" + dismiss */}
- + View Fundraising Event → - +