164 lines
5.4 KiB
TypeScript
Executable file
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>
|
|
);
|
|
}
|