146 lines
4.6 KiB
TypeScript
Executable file
146 lines
4.6 KiB
TypeScript
Executable file
import type { Metadata } from "next";
|
|
import { notFound } from "next/navigation";
|
|
import FacilitySearch from "@/app/FacilitySearch";
|
|
import PlaceMap from "@/components/PlaceMap";
|
|
import {
|
|
type FacilityRecord,
|
|
enrichFacilities,
|
|
filterFacilitiesByArea,
|
|
getAvailablePlaceConfigs,
|
|
getPlaceConfigFromSlug,
|
|
} from "@/app/facilityData";
|
|
import { API_URL } from "@/config/constants";
|
|
import {
|
|
createBreadcrumbJsonLd,
|
|
createCollectionPageJsonLd,
|
|
createItemListJsonLd,
|
|
createPageMetadata,
|
|
} from "@/app/seo";
|
|
|
|
export const dynamicParams = true;
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export async function generateStaticParams() {
|
|
return getAvailablePlaceConfigs().map((slug) => ({ slug }));
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>;
|
|
}): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const place = getPlaceConfigFromSlug(slug);
|
|
|
|
if (!place) {
|
|
return createPageMetadata({
|
|
title: "Sted ikke funnet",
|
|
description: "Denne stedssiden finnes ikke på TeeOff.",
|
|
path: `/sted/${slug}`,
|
|
});
|
|
}
|
|
|
|
return createPageMetadata({
|
|
title: place.title,
|
|
description: `${place.intro} TeeOff samler golfbaner i ${place.label} med oppdatert banestatus og baneprofiler.`,
|
|
path: `/sted/${slug}`,
|
|
});
|
|
}
|
|
|
|
export default async function PlacePage({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params;
|
|
const place = getPlaceConfigFromSlug(slug);
|
|
|
|
if (!place) {
|
|
notFound();
|
|
}
|
|
|
|
let facilities: FacilityRecord[] = [];
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/facilities`, {
|
|
next: { revalidate: 0 },
|
|
cache: "no-store",
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`API returnerte status ${res.status}`);
|
|
}
|
|
|
|
facilities = await res.json();
|
|
} catch (error) {
|
|
console.error("Kritisk feil ved henting av sted-data:", error);
|
|
facilities = [];
|
|
}
|
|
|
|
const safeData = Array.isArray(facilities) ? facilities : [];
|
|
const facilitiesInPlace = filterFacilitiesByArea(enrichFacilities(safeData), place.areaFilter);
|
|
const collectionJsonLd = createCollectionPageJsonLd({
|
|
name: place.title,
|
|
description: place.intro,
|
|
path: `/sted/${slug}`,
|
|
});
|
|
const itemListJsonLd = createItemListJsonLd({
|
|
name: place.title,
|
|
path: `/sted/${slug}`,
|
|
items: facilitiesInPlace
|
|
.filter((facility) => facility?.slug && facility?.name)
|
|
.map((facility) => ({
|
|
name: facility.name,
|
|
path: `/golfbaner/${facility.slug}`,
|
|
description: facility.description,
|
|
})),
|
|
});
|
|
const breadcrumbJsonLd = createBreadcrumbJsonLd([
|
|
{ name: "Hjem", path: "/" },
|
|
{ name: "Steder", path: "/sted/norge" },
|
|
{ name: place.label, path: `/sted/${slug}` },
|
|
]);
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
|
|
/>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(itemListJsonLd) }}
|
|
/>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
|
|
/>
|
|
<main className="site-shell min-h-screen">
|
|
<section className="mx-auto max-w-[1400px] px-4 py-8 sm:px-6 sm:py-10 lg:px-8 lg:py-12">
|
|
<div className="max-w-4xl">
|
|
<p className="mb-3 text-[11px] font-extrabold uppercase tracking-[0.3em] text-[#6FA786]">Steder</p>
|
|
<h1 className="section-title text-4xl text-[#112015] sm:text-5xl lg:text-6xl">{place.title}</h1>
|
|
<p className="mt-4 max-w-3xl text-base leading-8 text-[#617063]">{place.intro}</p>
|
|
<div className="mt-5 flex flex-wrap gap-3">
|
|
<span className="rounded-full bg-white px-4 py-2 text-[11px] font-extrabold uppercase tracking-[0.18em] text-[#112015] shadow-sm">
|
|
{facilitiesInPlace.length} golfbaner
|
|
</span>
|
|
<span className="rounded-full bg-[#25312A] px-4 py-2 text-[11px] font-extrabold uppercase tracking-[0.18em] text-white">
|
|
Kart og liste i samme visning
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-8">
|
|
<PlaceMap facilities={facilitiesInPlace} placeLabel={place.label} />
|
|
</div>
|
|
</section>
|
|
|
|
<FacilitySearch
|
|
initialFacilities={safeData}
|
|
variant="catalog"
|
|
title={place.title}
|
|
intro={`Filtrer golfbanene i ${place.label} videre etter banestatus, antall hull og andre egenskaper.`}
|
|
fixedAreaFilter={place.areaFilter}
|
|
hideTitleBlock
|
|
/>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|