Créer un compte
- {invite
- ? `Vous avez été invité à rejoindre « ${invite.orgName} » comme membre CE.`
- : "Un compte vous permet de réserver un séjour ou, en tant qu'hôte, de publier votre carbet."}
+ Un compte vous permet de réserver un séjour ou, en tant qu'hôte, de publier votre carbet.
-
+
Déjà un compte ?{" "}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ffcc89e..2a05155 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -5,8 +5,6 @@ import { PluginProvider } from "@/lib/plugins/client";
import { getEnabledPluginKeys, syncPluginsFromRegistry } from "@/lib/plugins/server";
import { SeasonBanner } from "@/components/SeasonBanner";
import { SiteHeaderGuard } from "@/components/SiteHeaderGuard";
-import { RentalCartProvider } from "@/components/RentalCartProvider";
-import { readCartFromCookies } from "@/lib/rental-cart-server";
import { LocaleProvider } from "@/lib/i18n/client";
import { dict, getLocale } from "@/lib/i18n/server";
@@ -54,21 +52,6 @@ export const metadata: Metadata = {
},
description:
"Karbé, la marketplace de location de carbets fluviaux de Guyane.",
- manifest: "/manifest.webmanifest",
- applicationName: "Karbé",
- appleWebApp: {
- capable: true,
- statusBarStyle: "black-translucent",
- title: "Karbé",
- },
- icons: {
- icon: [
- { url: "/icons/favicon-32.png", sizes: "32x32", type: "image/png" },
- { url: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
- { url: "/icons/icon-512.png", sizes: "512x512", type: "image/png" },
- ],
- apple: "/icons/apple-touch-icon.png",
- },
openGraph: {
type: "website",
siteName: "Karbé",
@@ -79,13 +62,6 @@ export const metadata: Metadata = {
},
};
-export const viewport = {
- themeColor: "#059669",
- width: "device-width",
- initialScale: 1,
- viewportFit: "cover" as const,
-};
-
export default async function RootLayout({
children,
}: Readonly<{
@@ -114,7 +90,6 @@ export default async function RootLayout({
const locale = await getLocale();
const messages = await dict(locale);
- const initialCart = await readCartFromCookies();
return (
-
-
-
- {children}
-
+
+
+ {children}
diff --git a/src/app/materiel/[itemId]/_components/AddToCart.tsx b/src/app/materiel/[itemId]/_components/AddToCart.tsx
deleted file mode 100644
index 0ee7e36..0000000
--- a/src/app/materiel/[itemId]/_components/AddToCart.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import Link from "next/link";
-
-import { useCart } from "@/components/RentalCartProvider";
-import { diffDays } from "@/lib/rental-cart";
-
-type Props = {
- itemId: string;
- pricePerDay: number;
- deposit: number;
- maxQty: number;
-};
-
-function todayPlus(n: number): string {
- const d = new Date();
- d.setHours(0, 0, 0, 0);
- d.setDate(d.getDate() + n);
- return d.toISOString().slice(0, 10);
-}
-
-export function AddToCart({ itemId, pricePerDay, deposit, maxQty }: Props) {
- const { addEntry, cart } = useCart();
- const [start, setStart] = useState(todayPlus(7));
- const [end, setEnd] = useState(todayPlus(9));
- const [qty, setQty] = useState(1);
- const [added, setAdded] = useState(false);
-
- const nights = Math.max(1, diffDays(start, end));
- const subtotal = nights * qty * pricePerDay;
- const depositTotal = qty * deposit;
-
- const alreadyInCart = cart.items.some(
- (e) => e.itemId === itemId && e.startDate === start && e.endDate === end,
- );
-
- function onAdd() {
- addEntry({ itemId, qty, startDate: start, endDate: end });
- setAdded(true);
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {pricePerDay.toFixed(0)} € × {nights} jour{nights > 1 ? "s" : ""} × {qty}
-
- {subtotal.toFixed(2)} €
-
- {depositTotal > 0 ? (
-
- + Caution (récupérable)
- {depositTotal.toFixed(2)} €
-
- ) : null}
-
-
- {!added ? (
-
- ) : (
-
-
- ✓ Ajouté au panier
-
-
- Voir mon panier
-
-
- )}
-
- );
-}
diff --git a/src/app/materiel/[itemId]/_components/AvailabilityPreview.tsx b/src/app/materiel/[itemId]/_components/AvailabilityPreview.tsx
deleted file mode 100644
index 4cdf5d9..0000000
--- a/src/app/materiel/[itemId]/_components/AvailabilityPreview.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-
-type Day = {
- date: string;
- availableQty: number;
- bookedQty: number;
- totalQty: number;
-};
-
-export function AvailabilityPreview({ itemId }: { itemId: string }) {
- const [calendar, setCalendar] = useState
(null);
-
- useEffect(() => {
- const today = new Date();
- today.setUTCHours(0, 0, 0, 0);
- const to = new Date(today.getTime() + 30 * 86_400_000);
- const fromStr = today.toISOString().slice(0, 10);
- const toStr = to.toISOString().slice(0, 10);
- fetch(`/api/rentals/items/${itemId}/availability?from=${fromStr}&to=${toStr}`)
- .then((r) => (r.ok ? r.json() : null))
- .then((j) => {
- if (j?.calendar) setCalendar(j.calendar);
- })
- .catch(() => {});
- }, [itemId]);
-
- if (!calendar) {
- return ;
- }
-
- return (
-
-
- Disponibilité sur les 30 prochains jours (vert = stock dispo, gris = épuisé) :
-
-
- {calendar.map((d) => {
- const ratio = d.availableQty / Math.max(1, d.totalQty);
- const tone =
- d.availableQty === 0 ? "bg-zinc-300" :
- ratio < 0.3 ? "bg-amber-300" :
- "bg-emerald-400";
- return (
-
- );
- })}
-
-
- );
-}
diff --git a/src/app/materiel/[itemId]/_components/ItemGallery.tsx b/src/app/materiel/[itemId]/_components/ItemGallery.tsx
deleted file mode 100644
index 7d6ba55..0000000
--- a/src/app/materiel/[itemId]/_components/ItemGallery.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-"use client";
-
-import { useState } from "react";
-
-type Media = { id: string; type: "PHOTO" | "VIDEO"; s3Url: string };
-
-export function ItemGallery({
- media,
- fallbackEmoji,
- alt,
-}: {
- media: Media[];
- fallbackEmoji: string;
- alt: string;
-}) {
- const [idx, setIdx] = useState(0);
-
- if (media.length === 0) {
- return (
-
- {fallbackEmoji}
-
- );
- }
-
- const current = media[idx];
-
- return (
-
-
- {current.type === "VIDEO" ? (
-
- ) : (
- // eslint-disable-next-line @next/next/no-img-element
-

- )}
-
- {media.length > 1 ? (
-
- {media.map((m, i) => (
-
- ))}
-
- ) : null}
-
- );
-}
diff --git a/src/app/materiel/[itemId]/page.tsx b/src/app/materiel/[itemId]/page.tsx
deleted file mode 100644
index d9a56b5..0000000
--- a/src/app/materiel/[itemId]/page.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import type { Metadata } from "next";
-import Link from "next/link";
-import { notFound } from "next/navigation";
-
-import { requirePluginOr404 } from "@/lib/plugins/guard";
-import { getPublicRentalItem } from "@/lib/rentals-public";
-import { RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels";
-
-import { AddToCart } from "./_components/AddToCart";
-import { AvailabilityPreview } from "./_components/AvailabilityPreview";
-import { ItemGallery } from "./_components/ItemGallery";
-
-export const dynamic = "force-dynamic";
-
-type PageProps = { params: Promise<{ itemId: string }> };
-
-export async function generateMetadata({ params }: PageProps): Promise {
- const { itemId } = await params;
- const item = await getPublicRentalItem(itemId);
- if (!item) return { title: "Item introuvable", robots: { index: false } };
- return {
- title: `${item.name} — Location matériel`,
- description: item.description ?? `Location de ${item.name} via ${item.provider.name}.`,
- };
-}
-
-export default async function RentalItemDetailPage({ params }: PageProps) {
- await requirePluginOr404("gear-rental");
- const { itemId } = await params;
- const item = await getPublicRentalItem(itemId);
- if (!item) notFound();
-
- const categoryEmoji =
- item.category === "SLEEP" ? "💤" :
- item.category === "NAVIGATION" ? "🛶" :
- item.category === "FISHING" ? "🎣" :
- item.category === "COOKING" ? "🍳" : "🦺";
-
- return (
-
-
- ← Tout le matériel
-
-
-
-
-
-
- {RENTAL_CATEGORY_LABEL[item.category]}
-
- {item.name}
-
- Loué par {item.provider.name}
- {item.provider.isSystemD ? (
-
- Fournisseur Karbé
-
- ) : null}
-
-
-
-
- 0
- ? item.media
- : item.imageUrl
- ? [{ id: "legacy", type: "PHOTO", s3Url: item.imageUrl }]
- : []
- }
- alt={item.name}
- fallbackEmoji={categoryEmoji}
- />
-
-
- {item.description ? (
-
- Description
- {item.description}
-
- ) : null}
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/materiel/_components/rental-filters.tsx b/src/app/materiel/_components/rental-filters.tsx
deleted file mode 100644
index 90dc76e..0000000
--- a/src/app/materiel/_components/rental-filters.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import Link from "next/link";
-
-import { RentalCategory } from "@/generated/prisma/enums";
-import { RENTAL_CATEGORIES, RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels";
-
-type Props = {
- filters: {
- q?: string;
- category?: RentalCategory;
- providerId?: string;
- river?: string;
- };
- rivers: string[];
- providers: { id: string; name: string; isSystemD: boolean }[];
-};
-
-export function RentalFilters({ filters, rivers, providers }: Props) {
- return (
-
- );
-}
diff --git a/src/app/materiel/_components/rental-item-card.tsx b/src/app/materiel/_components/rental-item-card.tsx
deleted file mode 100644
index 179750b..0000000
--- a/src/app/materiel/_components/rental-item-card.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import Link from "next/link";
-
-import type { PublicRentalItem } from "@/lib/rentals-public";
-import { RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels";
-
-export function RentalItemCard({ item }: { item: PublicRentalItem }) {
- return (
-
-
- {item.imageUrl ? (
- // eslint-disable-next-line @next/next/no-img-element
-

- ) : (
-
- {item.category === "SLEEP" ? "💤" :
- item.category === "NAVIGATION" ? "🛶" :
- item.category === "FISHING" ? "🎣" :
- item.category === "COOKING" ? "🍳" : "🦺"}
-
- )}
-
- {RENTAL_CATEGORY_LABEL[item.category]}
-
- {item.provider.isSystemD ? (
-
- Karbé
-
- ) : null}
-
-
-
- {item.name}
-
-
{item.provider.name}
-
{item.description ?? ""}
-
- {item.withMotor ? (
- ⚙️ moteur
- ) : null}
- {item.requiresLicense ? (
- 🪪 permis
- ) : null}
- {item.fuelIncluded ? (
- ⛽ essence
- ) : null}
- {Number(item.deposit) > 0 ? (
-
- Caution {Number(item.deposit).toFixed(0)} €
-
- ) : null}
-
-
-
-
- {Number(item.pricePerDay).toFixed(0)} €
-
- / jour
-
- {item.pricePerWeek ? (
-
- {Number(item.pricePerWeek).toFixed(0)} € / semaine
-
- ) : null}
-
-
-
- );
-}
diff --git a/src/app/materiel/layout.tsx b/src/app/materiel/layout.tsx
deleted file mode 100644
index d6cebd2..0000000
--- a/src/app/materiel/layout.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import { requirePluginOr404 } from "@/lib/plugins/guard";
-
-export default async function MaterielLayout({ children }: { children: React.ReactNode }) {
- await requirePluginOr404("gear-rental");
- return <>{children}>;
-}
diff --git a/src/app/materiel/page.tsx b/src/app/materiel/page.tsx
deleted file mode 100644
index 31fcd77..0000000
--- a/src/app/materiel/page.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import type { Metadata } from "next";
-
-import { RentalCategory } from "@/generated/prisma/enums";
-import { requirePluginOr404 } from "@/lib/plugins/guard";
-import { isRentalCategory } from "@/lib/rental-category-labels";
-import {
- listPublicProviders,
- listPublicRentalItems,
- listPublicRivers,
-} from "@/lib/rentals-public";
-
-import { RentalFilters } from "./_components/rental-filters";
-import { RentalItemCard } from "./_components/rental-item-card";
-
-export const dynamic = "force-dynamic";
-
-export const metadata: Metadata = {
- title: "Louer du matériel",
- description:
- "Hamac, moustiquaire, pirogue, kayak, barque, gilet, réchaud… Toutes les locations de matériel pour réussir votre séjour en carbet guyanais, fournies par l'association System D et des prestataires locaux validés.",
-};
-
-type PageProps = {
- searchParams: Promise<{
- q?: string;
- category?: string;
- providerId?: string;
- river?: string;
- }>;
-};
-
-export default async function MaterialPage({ searchParams }: PageProps) {
- await requirePluginOr404("gear-rental");
- const sp = await searchParams;
- const filters = {
- q: sp.q?.trim() || undefined,
- category: sp.category && isRentalCategory(sp.category) ? (sp.category as RentalCategory) : undefined,
- providerId: sp.providerId || undefined,
- river: sp.river || undefined,
- };
- const [items, providers, rivers] = await Promise.all([
- listPublicRentalItems(filters),
- listPublicProviders(),
- listPublicRivers(),
- ]);
-
- return (
-
-
-
- Matériel à louer
-
-
- Hamac, moustiquaire, pirogue, kayak, barque, réchaud, gilet de sauvetage…
- Tout le matériel pour réussir votre séjour, mis à disposition par
- l'association System D ou par des prestataires
- locaux validés.
-
-
-
-
-
-
-
- {items.length} item{items.length > 1 ? "s" : ""} disponible
- {items.length > 1 ? "s" : ""}
-
- {items.length === 0 ? (
-
- Aucun item ne correspond à votre recherche. Essayez d'élargir
- les filtres.
-
- ) : (
-
- {items.map((item) => (
- -
-
-
- ))}
-
- )}
-
-
- {providers.length > 0 ? (
-
-
- Nos prestataires partenaires
-
-
- {providers.length} prestataire{providers.length > 1 ? "s" : ""} valid
- {providers.length > 1 ? "és" : "é"} sur Karbé.
-
-
- {providers.map((p) => (
- -
-
-
{p.name}
- {p.isSystemD ? (
-
- Karbé
-
- ) : null}
-
-
- Fleuves : {p.rivers.join(", ") || "—"} · {p.itemsCount} item
- {p.itemsCount > 1 ? "s" : ""}
-
- {p.description ? (
-
- {p.description}
-
- ) : null}
-
- ))}
-
-
- ) : null}
-
- );
-}
diff --git a/src/app/mes-favoris/page.tsx b/src/app/mes-favoris/page.tsx
deleted file mode 100644
index 5887400..0000000
--- a/src/app/mes-favoris/page.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { redirect } from "next/navigation";
-import Link from "next/link";
-
-import { auth } from "@/auth";
-import { listFavoriteCarbets } from "@/lib/reels";
-import { buildSrcSet } from "@/lib/image-variants";
-
-export const dynamic = "force-dynamic";
-
-export const metadata = { title: "Mes favoris" };
-
-export default async function MyFavoritesPage() {
- const session = await auth();
- if (!session?.user?.id) redirect("/connexion?next=/mes-favoris");
-
- const carbets = await listFavoriteCarbets(session.user.id);
-
- return (
-
- Mes favoris
-
- {carbets.length === 0
- ? "Aucun favori pour l'instant — ajoutez des carbets depuis le mode Au fil de l'eau ou les fiches."
- : `${carbets.length} carbet${carbets.length > 1 ? "s" : ""} sauvegardé${carbets.length > 1 ? "s" : ""}.`}
-
-
- {carbets.length === 0 ? (
-
-
- Découvrir des carbets
-
-
- ) : (
-
- {carbets.map((c) => (
- -
-
- {c.media[0] ? (
- // eslint-disable-next-line @next/next/no-img-element
-
- ) : (
-
- )}
-
-
{c.title}
-
- {c.river} · {Number(c.nightlyPrice).toFixed(0)} € / nuit
-
-
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/src/app/mes-locations/page.tsx b/src/app/mes-locations/page.tsx
deleted file mode 100644
index 53f7eb6..0000000
--- a/src/app/mes-locations/page.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import Link from "next/link";
-
-import { CancelRentalButton } from "@/components/CancelRentalButton";
-import { requireAuth } from "@/lib/authorization";
-import { requirePluginOr404 } from "@/lib/plugins/guard";
-import { prisma } from "@/lib/prisma";
-import { RENTAL_CATEGORY_LABEL } from "@/lib/rental-category-labels";
-
-export const dynamic = "force-dynamic";
-
-export const metadata = { title: "Mes locations matériel" };
-
-const STATUS_LABEL: Record = {
- PENDING: "En attente",
- CONFIRMED: "Confirmée",
- HANDED_OVER: "Remis",
- RETURNED: "Retourné",
- CANCELLED: "Annulée",
-};
-
-const PAYMENT_LABEL: Record = {
- PENDING: "Paiement en attente",
- AUTHORIZED: "Paiement autorisé",
- SUCCEEDED: "Paiement reçu",
- FAILED: "Paiement échoué",
- REFUNDED: "Remboursé",
-};
-
-type SearchParams = Promise<{ payment?: string; ids?: string; ok?: string }>;
-
-export default async function MyRentalsPage({ searchParams }: { searchParams: SearchParams }) {
- await requirePluginOr404("gear-rental");
- const session = await requireAuth();
- const sp = await searchParams;
-
- const rentals = await prisma.rentalBooking.findMany({
- where: { tenantId: session.user.id },
- orderBy: [{ startDate: "desc" }],
- include: {
- provider: { select: { id: true, name: true, isSystemD: true, contactPhone: true, contactEmail: true } },
- lines: { include: { item: { select: { id: true, name: true, category: true, imageUrl: true } } } },
- booking: { select: { id: true, carbet: { select: { slug: true, title: true } } } },
- },
- });
-
- const dateFmt = new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "long", year: "numeric" });
-
- const showSuccess = sp.payment === "success" || sp.ok;
-
- return (
-
-
-
- {showSuccess ? (
-
- ✓ Votre commande de matériel a bien été enregistrée. Vous recevrez un email de confirmation.
-
- ) : null}
-
- {rentals.length === 0 ? (
-
- Vous n'avez pas encore loué de matériel.{" "}
-
- Découvrir le matériel disponible
-
- .
-
- ) : (
-
- {rentals.map((rb) => (
- -
-
-
-
{rb.provider.name}
- {rb.booking?.carbet ? (
-
- Pour le séjour{" "}
-
- {rb.booking.carbet.title}
-
-
- ) : (
-
Location indépendante
- )}
-
-
-
- {STATUS_LABEL[rb.status] ?? rb.status}
-
-
- {PAYMENT_LABEL[rb.paymentStatus] ?? rb.paymentStatus}
-
-
-
-
-
- Du {dateFmt.format(rb.startDate)} au {dateFmt.format(rb.endDate)}
-
-
-
- {rb.lines.map((line) => (
- -
-
- {line.qty}×{" "}
-
- {line.item.name}
-
-
- {RENTAL_CATEGORY_LABEL[line.item.category]}
-
-
-
- {Number(line.lineTotal).toFixed(2)} €
-
-
- ))}
-
-
-
- Total
-
- {Number(rb.amount).toFixed(2)} {rb.currency}
-
-
-
- {(rb.provider.contactPhone || rb.provider.contactEmail) && rb.status !== "CANCELLED" ? (
-
- Contact prestataire :{" "}
- {rb.provider.contactPhone ? 📞 {rb.provider.contactPhone} : null}
- {rb.provider.contactEmail ? ✉ {rb.provider.contactEmail} : null}
-
- ) : null}
-
- {(rb.status === "PENDING" || rb.status === "CONFIRMED") ? (
-
-
-
- ) : null}
-
- ))}
-
- )}
-
- );
-}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index ad5f2bd..5d0099b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,9 +1,63 @@
-import { redirect } from "next/navigation";
+import Link from "next/link";
+import { IfPluginEnabled } from "@/components/IfPluginEnabled";
+import { HeroSection } from "@/components/landing/HeroSection";
+import { ExperiencesSection } from "@/components/landing/ExperiencesSection";
+import { HowItWorksSection } from "@/components/landing/HowItWorksSection";
+import { CESection } from "@/components/landing/CESection";
+import { TestimonialsSection } from "@/components/landing/TestimonialsSection";
+import { LandingFooter } from "@/components/landing/Footer";
/**
- * Home redirige vers le mode immersif « Au fil de l'eau » par défaut.
- * L'ancien hero/landing reste accessible via /accueil.
+ * Page d'accueil — la majorité du contenu est conditionnée par les plugins :
+ * - `landing-hero` → hero plein écran
+ * - `landing-sections` → 2 expériences + comment ça marche + CE + témoignages + footer riche
+ *
+ * Si aucun de ces plugins n'est activé, on retombe sur la home historique
+ * minimaliste (fallback). Activable depuis /admin/plugins.
*/
export default function Home() {
- redirect("/decouvrir");
+ return (
+ <>
+
+
+
+ Karbé — carbets fluviaux de Guyane
+
+
+ La marketplace pour louer des carbets le long des fleuves de Guyane.
+
+
+
+ Découvrir les carbets
+
+
+ Espace hôte
+
+
+
+
+ }
+ >
+