Nye-TeeOff/frontend/src/app/HeroSlider.tsx
2026-04-14 16:31:28 +02:00

164 lines
5.4 KiB
TypeScript
Executable file

"use client";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
const MANUAL_EXCLUSION_LIST = [
"alsten-golfklubb",
"askim-golfklubb",
"bergen-golfklubb",
"eidskog-golfklubb",
"eiker-golfklubb",
"floro-golfklubb",
"garder-golfklubb",
"hafjell-golfklubb",
"halden-golfklubb",
"haugesund-golfklubb",
"hinnoy-golfklubb",
"hitra-golfklubb",
"hurum-golfklubb",
"imjelt-pitch-putt",
"karmoy-golfklubb",
"kristiansund-og-omegn-golfklubb",
"lommedalen-golfklubb",
"laerdal-golfklubb",
"moa-golfsenter",
"modum-golfklubb",
"nes-golfklubb-09",
"nittedal-golfklubb",
"selbu-golfklubb",
"stryn-golfklubb",
"sunnfjord-golfklubb",
"tysnes-golfklubb",
"vanylven-golfklubb",
"vesteralen-golfklubb",
"vestlia-golf",
];
const GOOD_STATUSES = ["aapen", "aapner_snart", "stenger_snart", "aapen_med_vintergreener"];
const BAD_STATUSES = ["nedlagt", "under_utvikling"];
type CourseStatus = {
status?: string;
};
type Facility = {
id: number;
slug: string;
name: string;
image_url?: string | null;
course_statuses?: CourseStatus[] | null;
};
export default function HeroSlider({ facilities }: { facilities: Facility[] }) {
const [currentIndex, setCurrentIndex] = useState(0);
const sliderItems = useMemo(() => {
if (!Array.isArray(facilities) || facilities.length === 0) return [];
const validFacilities = facilities.filter((facility) => {
if (MANUAL_EXCLUSION_LIST.includes(facility.slug)) return false;
const image = String(facility.image_url || "").toLowerCase();
if (!image || image.includes("standard")) return false;
const statuses = Array.isArray(facility.course_statuses) ? facility.course_statuses : [];
const hasForbiddenStatus = statuses.some((status) =>
BAD_STATUSES.includes(String(status?.status || "").toLowerCase())
);
return !hasForbiddenStatus;
});
const priority = validFacilities.filter((facility) => {
const statuses = Array.isArray(facility.course_statuses) ? facility.course_statuses : [];
return statuses.some((status) => GOOD_STATUSES.includes(String(status?.status || "").toLowerCase()));
});
const fallback = validFacilities.filter((facility) => !priority.includes(facility));
const now = new Date();
const seed = Number(`${now.getFullYear()}${now.getMonth()}${now.getDate()}${now.getHours()}`);
const seededShuffle = (items: Facility[]) =>
[...items].sort((a, b) => ((a.id * seed) % 101) - ((b.id * seed) % 101));
const selected = [...seededShuffle(priority), ...seededShuffle(fallback)].slice(0, 5);
return selected;
}, [facilities]);
useEffect(() => {
if (sliderItems.length <= 1) return;
const interval = setInterval(() => {
setCurrentIndex((previous) => (previous + 1) % sliderItems.length);
}, 8000);
return () => clearInterval(interval);
}, [sliderItems.length]);
if (sliderItems.length === 0) return null;
return (
<section className="relative min-h-[420px] overflow-hidden bg-[#25312A] sm:min-h-[520px] lg:min-h-[620px]">
{sliderItems.map((facility, index) => (
<div
key={facility.id}
className={`absolute inset-0 transition-opacity duration-1000 ${
index === currentIndex ? "opacity-100" : "pointer-events-none opacity-0"
}`}
>
<Image
src={facility.image_url || "/Toppbilde-standard.jpg"}
alt={facility.name}
fill
priority={index === currentIndex}
sizes="100vw"
className="object-cover object-center"
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#25312A]/70 via-[#25312A]/10 to-[#25312A]/20" />
</div>
))}
<div className="absolute inset-0 z-10 mx-auto flex max-w-[1400px] flex-col justify-between px-4 py-5 sm:px-6 sm:py-7 lg:px-8 lg:py-10">
<div className="flex justify-end">
<div className="max-w-[14rem] text-right sm:max-w-[22rem] lg:max-w-[48rem]">
<h1 className="text-sm font-bold uppercase leading-tight tracking-tight text-white sm:text-3xl lg:text-5xl">
<span>TeeOff.no gir deg komplett oversikt over </span>
<Link
href="/golfbaner"
className="font-extrabold transition hover:text-white"
style={{ color: "#FF5722" }}
aria-label="Se alle golfbaner"
>
ALLE
</Link>
<span> norske golfbaner!</span>
</h1>
</div>
</div>
<div className="flex flex-col items-center gap-4">
<Link
href={`/golfbaner/${sliderItems[currentIndex].slug}`}
className="btn btn-lg btn-primary text-center text-xl tracking-tight shadow-lg sm:px-8 sm:text-3xl lg:text-4xl"
>
{sliderItems[currentIndex].name}
</Link>
<div className="flex gap-3">
{sliderItems.map((_, index) => (
<button
key={index}
type="button"
onClick={() => setCurrentIndex(index)}
aria-label={`Vis bilde ${index + 1}`}
className={`h-1.5 rounded-full transition-all ${
index === currentIndex ? "w-12 bg-[#8BC34A]" : "w-5 bg-white/35"
}`}
/>
))}
</div>
</div>
</div>
</section>
);
}