63 lines
1.5 KiB
TypeScript
63 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { BadgeEmbed } from "@/components/BadgeEmbed";
|
|
|
|
export type PublicFooterBadge = {
|
|
id: string;
|
|
name: string;
|
|
embedHtml: string | null;
|
|
imageUrl: string | null;
|
|
linkUrl: string | null;
|
|
sortOrder: number;
|
|
};
|
|
|
|
type Props = {
|
|
initialBadges?: PublicFooterBadge[];
|
|
};
|
|
|
|
export function FooterBadgeMarquee({ initialBadges = [] }: Props) {
|
|
const [badges, setBadges] = useState<PublicFooterBadge[]>(initialBadges);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
fetch("/api/badges")
|
|
.then(async (res) => {
|
|
if (!res.ok) return;
|
|
const data = await res.json();
|
|
if (!cancelled && Array.isArray(data.badges)) {
|
|
setBadges(data.badges);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
if (badges.length === 0) return null;
|
|
|
|
// Duplicate track for seamless infinite scroll
|
|
const loop = [...badges, ...badges];
|
|
const durationSec = Math.max(18, badges.length * 10);
|
|
|
|
return (
|
|
<div className="badge-marquee mb-12" aria-label="Featured on">
|
|
<div
|
|
className="badge-marquee-track"
|
|
style={{ ["--badge-marquee-duration" as string]: `${durationSec}s` }}
|
|
>
|
|
{loop.map((badge, index) => (
|
|
<div
|
|
key={`${badge.id}-${index}`}
|
|
className="badge-marquee-item"
|
|
aria-hidden={index >= badges.length ? true : undefined}
|
|
>
|
|
<BadgeEmbed badge={badge} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|