Implémente SYS-5 : la marketplace publique pour découvrir les carbets
fluviaux publiés par les hôtes.
- /carbets : page de recherche server-side avec filtres GET
(fleuve, dates de séjour, capacité min.), grille de résultats
avec photo de couverture, fleuve, capacité, durée pirogue
- /carbets/[slug] : fiche carbet SSR
- generateMetadata (title/description + OpenGraph/Twitter cards)
- galerie médias (photo couverture + vignettes vidéo/photo)
- description, équipements (catalogue), accès, coords GPS,
capacité, prénom de l'hôte
- robots.ts + sitemap.xml (incluant les carbets publiés)
- metadataBase / title.template au niveau du root layout, OG par
défaut Karbé
- Lien "Découvrir les carbets" sur la home
- Helpers partagés : lib/carbet-search.ts (parse filters + query),
lib/carbet-public.ts (fetch SSR mémoïsé via React cache),
lib/format.ts (durée pirogue, troncature, coords)
- Nouvelle variable d'env NEXT_PUBLIC_SITE_URL (canonical/OG/sitemap)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import type { Metadata } from "next";
|
|
|
|
import {
|
|
listPublishedRivers,
|
|
parseSearchFilters,
|
|
searchCarbets,
|
|
type RawSearchParams,
|
|
} from "@/lib/carbet-search";
|
|
|
|
import { CarbetCard } from "./_components/carbet-card";
|
|
import { SearchFilters } from "./_components/search-filters";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "Rechercher un carbet",
|
|
description:
|
|
"Explorez les carbets fluviaux de Guyane disponibles sur Karbé : filtrez par fleuve, dates de séjour et capacité d'accueil.",
|
|
alternates: { canonical: "/carbets" },
|
|
openGraph: {
|
|
title: "Rechercher un carbet — Karbé",
|
|
description:
|
|
"Trouvez un carbet authentique le long des fleuves de Guyane. Filtrez par fleuve, dates et capacité.",
|
|
type: "website",
|
|
},
|
|
};
|
|
|
|
export default async function CarbetsSearchPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<RawSearchParams>;
|
|
}) {
|
|
const raw = await searchParams;
|
|
const filters = parseSearchFilters(raw);
|
|
|
|
const [results, rivers] = await Promise.all([
|
|
searchCarbets(filters),
|
|
listPublishedRivers(),
|
|
]);
|
|
|
|
const hasActiveFilters = Boolean(
|
|
filters.river ||
|
|
filters.startDate ||
|
|
filters.endDate ||
|
|
filters.capacity,
|
|
);
|
|
|
|
return (
|
|
<main className="mx-auto w-full max-w-6xl flex-1 px-6 py-10">
|
|
<header className="mb-6">
|
|
<h1 className="text-3xl font-semibold text-zinc-900 sm:text-4xl">
|
|
Carbets fluviaux de Guyane
|
|
</h1>
|
|
<p className="mt-2 max-w-2xl text-base text-zinc-600">
|
|
Sélectionnez votre fleuve, vos dates et le nombre de voyageurs pour
|
|
découvrir les carbets disponibles, depuis le Maroni jusqu'à
|
|
l'Oyapock.
|
|
</p>
|
|
</header>
|
|
|
|
<SearchFilters filters={filters} rivers={rivers} />
|
|
|
|
<section className="mt-8" aria-live="polite">
|
|
<h2 className="sr-only">Résultats de la recherche</h2>
|
|
{results.length === 0 ? (
|
|
<p className="rounded-md border border-dashed border-zinc-300 px-6 py-12 text-center text-sm text-zinc-500">
|
|
{hasActiveFilters
|
|
? "Aucun carbet ne correspond à votre recherche. Essayez d'élargir les filtres."
|
|
: "Aucun carbet publié pour le moment. Revenez bientôt !"}
|
|
</p>
|
|
) : (
|
|
<>
|
|
<p className="mb-4 text-sm text-zinc-600">
|
|
{results.length} carbet{results.length > 1 ? "s" : ""} trouvé
|
|
{results.length > 1 ? "s" : ""}.
|
|
</p>
|
|
<ul className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
{results.map((carbet) => (
|
|
<li key={carbet.id}>
|
|
<CarbetCard carbet={carbet} />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|