/* BoatsFun — Page demande de réservation V1 (checkout-combine.html) Réservation simple via ?boat=slug, ou combinée via ?slugs=...®ion=...&date=...&slot=... Affiche le récap des bateaux + totaux ESTIMÉS (aucun paiement), le mode d'opération, les services sur demande et un formulaire "Vos informations". CTA "Envoyer la demande de réservation" → handleReservationRequestSubmit : regroupe la demande dans un objet et fait un console.log (Zapier/email à brancher post-V1). */ (function () { const { I } = window.BF; const { BOATS, bySlug, blockTotal, depositFor, TIME_SLOTS } = window.BFBoats; const { useState, useMemo } = React; const COMBINE_KEY = "bf.combine.selection"; // Images temporaires (Unsplash) — style nautique / premium / lifestyle. // À remplacer par les visuels finaux dans assets/ quand fournis. const SERVICES = [ { id: "crew", title: "Équipage à bord", desc: "Personnel de bord supplémentaire pour accompagner votre sortie.", img: "https://images.unsplash.com/photo-1540541338287-41700207dee6?w=800&q=80&auto=format&fit=crop" }, { id: "traiteur", title: "Service traiteur", desc: "Repas préparés, bouchées ou service complet selon votre événement.", img: "https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800&q=80&auto=format&fit=crop" }, { id: "dj", title: "DJ privé", desc: "Un DJ pour créer une ambiance festive directement à bord.", img: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800&q=80&auto=format&fit=crop" }, { id: "barman", title: "Barman privé", desc: "Service de cocktails et boissons préparées pendant la sortie.", img: "https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?w=800&q=80&auto=format&fit=crop" }, { id: "transport", title: "Transport privé / limousine / party bus", desc: "Transport premium pour arriver à la marina sans stress.", img: "https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=800&q=80&auto=format&fit=crop" }, { id: "media", title: "Photos & vidéo souvenir", desc: "Capturez votre sortie avec du contenu photo ou vidéo professionnel.", img: "https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?w=800&q=80&auto=format&fit=crop" }, ]; // Critères d'admissibilité pour l'option « opérer le bateau soi-même » (tous requis). const ELIG_ITEMS = [ { k: "competence", label: "Je confirme avoir une preuve de compétence valide pour opérer une embarcation motorisée." }, { k: "experience", label: "Je confirme avoir au moins 2 ans d'expérience de navigation pertinente." }, { k: "similaire", label: "J'ai déjà opéré un bateau de taille similaire." }, { k: "manoeuvres", label: "Je suis à l'aise avec les manœuvres, l'accostage, les règles de navigation et les conditions météo." }, { k: "documents", label: "Je comprends que BoatsFun peut demander des documents justificatifs avant d'approuver ma demande." }, ]; function readParams() { if (typeof window === "undefined") return { slugs: [], boat: "", region: "", date: "", timeSlot: "" }; const p = new URLSearchParams(window.location.search); const slugsParam = p.get("slugs") || ""; const slugs = slugsParam.split(",").map((s) => s.trim()).filter(Boolean); return { slugs, boat: (p.get("boat") || "").trim(), // réservation simple depuis une fiche bateau region: p.get("region") || "", date: p.get("date") || "", timeSlot: p.get("slot") || "" }; } function readFallback() { try { const raw = localStorage.getItem(COMBINE_KEY); if (!raw) return null; return JSON.parse(raw); } catch (e) { return null; } } // Formatte les zones de navigation communes entre les bateaux sélectionnés function commonZones(boats) { if (!boats.length) return []; const set = new Set(boats[0].compatibleMeetingZones || []); boats.slice(1).forEach((b) => { const z = new Set(b.compatibleMeetingZones || []); for (const v of Array.from(set)) if (!z.has(v)) set.delete(v); }); return Array.from(set); } function formatDate(iso) { if (!iso) return "Non renseignée"; try { const d = new Date(iso + "T00:00:00"); return d.toLocaleDateString("fr-CA", { weekday: "long", day: "numeric", month: "long", year: "numeric" }); } catch (e) { return iso; } } const styles = ` .bf .cc-hero { background:linear-gradient(180deg, #002244 0%, #003a66 100%); color:#fff; padding:56px 0 48px; } .bf .cc-hero .eyebrow { color:var(--teal); } .bf .cc-hero h1 { font-family:'Poppins', sans-serif; font-size:42px; line-height:1.1; font-weight:800; color:#fff; margin:12px 0 12px; letter-spacing:-1.1px; } .bf .cc-hero p.lead { font-size:16.5px; line-height:1.55; color:rgba(255,255,255,.82); max-width:680px; } .bf .cc-wrap { padding:48px 0 var(--section-pad); } .bf .cc-grid { display:grid; grid-template-columns: 1.5fr 1fr; gap:28px; align-items:start; } /* Cartes bateaux */ .bf .cc-section { background:#fff; border:1px solid var(--hair); border-radius:18px; padding:24px 26px; } .bf .cc-section h2 { font-family:'Poppins', sans-serif; font-size:22px; font-weight:700; color:var(--navy); margin-bottom:18px; letter-spacing:-.4px; } .bf .cc-boats { display:flex; flex-direction:column; gap:16px; } .bf .cc-boat { display:grid; grid-template-columns: 120px 1fr auto; gap:16px; align-items:center; padding:12px; border:1px solid var(--hair); border-radius:14px; transition:border-color .15s; } .bf .cc-boat:hover { border-color:var(--navy); } .bf .cc-boat-img { width:120px; height:90px; border-radius:10px; overflow:hidden; background:var(--sea-mist); } .bf .cc-boat-img img { width:100%; height:100%; object-fit:cover; display:block; } .bf .cc-boat-info { min-width:0; } .bf .cc-boat-info h3 { font-family:'Poppins', sans-serif; font-size:16.5px; font-weight:700; color:var(--navy); line-height:1.25; letter-spacing:-.2px; margin-bottom:4px; } .bf .cc-boat-info .ccb-type { display:inline-block; background:var(--sea-mist); color:var(--navy); font-family:'Inter', sans-serif; font-size:11.5px; font-weight:600; padding:2px 9px; border-radius:999px; margin-right:6px; } .bf .cc-boat-info .ccb-meta { font-size:13px; color:var(--body); margin-top:6px; line-height:1.4; } .bf .cc-boat-info .ccb-meta svg { width:13px; height:13px; vertical-align:-2px; margin-right:4px; color:var(--teal); } .bf .cc-boat-price { text-align:right; } .bf .cc-boat-price .pp { font-family:'Poppins', sans-serif; font-size:18px; font-weight:700; color:var(--navy); } .bf .cc-boat-price .pd { font-size:12px; color:var(--muted); margin-top:3px; } /* Sidebar : récap + paiement */ .bf .cc-side { display:flex; flex-direction:column; gap:18px; position:sticky; top:96px; } .bf .cc-summary { background:#fff; border:1px solid var(--hair); border-radius:18px; padding:24px 26px; } .bf .cc-summary h2 { font-family:'Poppins', sans-serif; font-size:20px; font-weight:700; color:var(--navy); margin-bottom:14px; } .bf .cc-summary .ccs-row { display:flex; justify-content:space-between; padding:9px 0; border-bottom:1px dashed var(--hair); font-size:14px; color:var(--body); } .bf .cc-summary .ccs-row:last-of-type { border-bottom:none; } .bf .cc-summary .ccs-row b { color:var(--navy); font-weight:600; } .bf .cc-summary .ccs-totals { margin-top:18px; padding-top:18px; border-top:2px solid var(--hair); } .bf .cc-summary .ccs-tot { display:flex; justify-content:space-between; align-items:baseline; margin-bottom:8px; font-size:14.5px; color:var(--body); } .bf .cc-summary .ccs-tot.is-big { font-family:'Poppins', sans-serif; font-size:22px; font-weight:700; color:var(--navy); margin-bottom:0; } .bf .cc-summary .ccs-tot .amount { color:var(--navy); font-weight:700; } .bf .cc-summary .ccs-tot.is-big .amount { color:var(--orange); } .bf .cc-summary .ccs-cta { width:100%; margin-top:18px; background:var(--orange); border:none; color:#fff; font-family:'Inter', sans-serif; font-weight:600; font-size:15px; padding:15px 24px; border-radius:999px; cursor:pointer; box-shadow:var(--cta-shadow-orange); transition:all .2s; display:inline-flex; align-items:center; justify-content:center; gap:8px; } .bf .cc-summary .ccs-cta:hover { background:#FF9420; transform:translateY(-2px); box-shadow:var(--cta-shadow-orange-hover); } .bf .cc-summary .ccs-cta:disabled { opacity:.5; cursor:not-allowed; transform:none; } .bf .cc-note { background:var(--sea-mist); border-left:3px solid var(--teal); border-radius:10px; padding:14px 16px; font-size:13.5px; color:var(--body); line-height:1.55; } .bf .cc-note strong { color:var(--navy); display:block; margin-bottom:4px; font-size:13.5px; } .bf .cc-empty { text-align:center; padding:80px 24px; background:#fff; border:1px dashed var(--hair); border-radius:18px; } .bf .cc-empty h2 { font-family:'Poppins', sans-serif; font-size:22px; color:var(--navy); margin-bottom:8px; } .bf .cc-empty p { color:var(--body); margin-bottom:18px; } @media (max-width: 1024px) { .bf .cc-grid { grid-template-columns: 1fr; } .bf .cc-side { position:static; } } @media (max-width: 860px) { .bf .cc-hero h1 { font-size:30px; } .bf .cc-section, .bf .cc-summary { padding:18px 18px; } .bf .cc-boat { grid-template-columns: 90px 1fr; } .bf .cc-boat-img { width:90px; height:75px; } .bf .cc-boat-price { grid-column:1 / -1; text-align:left; padding-top:6px; border-top:1px dashed var(--hair); } } /* Add-ons */ .bf .cc-left { display:flex; flex-direction:column; gap:18px; } .bf .cc-addons-sec { background:#fff; border:1px solid var(--hair); border-radius:18px; padding:24px 26px; } .bf .cc-addons-sec h2 { font-family:'Poppins', sans-serif; font-size:22px; font-weight:700; color:var(--navy); margin:0 0 4px; letter-spacing:-.4px; } .bf .cc-addons-sub { font-size:14px; color:var(--muted); margin:0 0 20px; } .bf .cc-addons-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; } .bf .cc-addon-card { border:1.5px solid var(--hair); border-radius:16px; padding:0; cursor:pointer; transition:border-color .2s, box-shadow .25s, transform .2s; text-align:left; background:#fff; width:100%; box-sizing:border-box; overflow:hidden; display:flex; flex-direction:column; } .bf .cc-addon-card:hover { border-color:var(--teal); box-shadow:0 10px 26px rgba(0,34,68,.13); transform:translateY(-3px); } .bf .cc-addon-card.is-active { border-color:var(--teal); box-shadow:0 10px 26px rgba(0,181,226,.22); } .bf .cc-addon-media { position:relative; width:100%; aspect-ratio:16 / 10; background:linear-gradient(135deg,#002244,#00B5E2); overflow:hidden; } .bf .cc-addon-media img { width:100%; height:100%; object-fit:cover; display:block; transition:transform .4s ease; } .bf .cc-addon-card:hover .cc-addon-media img { transform:scale(1.05); } .bf .cc-addon-media::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,34,68,0) 50%, rgba(0,34,68,.30)); pointer-events:none; } .bf .cc-addon-badge { position:absolute; top:10px; left:10px; z-index:1; display:inline-flex; align-items:center; gap:5px; background:var(--teal); color:#fff; font-size:11px; font-weight:700; letter-spacing:.2px; padding:5px 10px; border-radius:999px; box-shadow:0 4px 12px rgba(0,34,68,.25); } .bf .cc-addon-body { padding:14px 16px 16px; display:flex; flex-direction:column; flex:1; } .bf .cc-addon-title { font-family:'Poppins', sans-serif; font-size:14.5px; font-weight:700; color:var(--navy); margin:0 0 6px; line-height:1.25; } .bf .cc-addon-desc { font-size:12.5px; color:var(--body); line-height:1.5; margin:0 0 14px; flex:1; } .bf .cc-addon-footer { display:flex; justify-content:space-between; align-items:center; margin-top:auto; } .bf .cc-addon-price { font-size:12px; color:var(--muted); font-style:italic; } .bf .cc-addon-toggle { width:32px; height:32px; border-radius:50%; border:1.5px solid rgba(0,34,68,.18); background:#fff; color:var(--navy); font-size:19px; line-height:1; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; transition:all .18s; padding:0; flex-shrink:0; } .bf .cc-addon-card:hover .cc-addon-toggle { border-color:var(--teal); color:var(--teal); } .bf .cc-addon-card.is-active .cc-addon-toggle { background:var(--teal); border-color:var(--teal); color:#fff; font-size:15px; font-weight:700; } .bf .cc-addons-note { margin-top:14px; background:var(--sea-mist); border-left:3px solid var(--teal); border-radius:10px; padding:12px 14px; font-size:12.5px; color:var(--body); line-height:1.55; } .bf .cc-addons-sum { margin-top:14px; padding-top:14px; border-top:1px dashed var(--hair); } .bf .cc-addons-sum-hd { font-size:13px; font-weight:700; color:var(--navy); margin-bottom:6px; } .bf .cc-addons-sum-row { display:flex; justify-content:space-between; font-size:13px; color:var(--body); padding:3px 0; } .bf .cc-addons-sum-row .sum-muted { color:var(--muted); } @media (max-width:960px) { .bf .cc-addons-grid { grid-template-columns:repeat(2,1fr); } } @media (max-width:600px) { .bf .cc-addons-grid { grid-template-columns:1fr; } .bf .cc-addons-sec { padding:18px; } } `; const opStyles = ` /* Mode d'opération */ .bf .cc-op-sec { background:#fff; border:1px solid var(--hair); border-radius:18px; padding:24px 26px; } .bf .cc-op-sec h2 { font-family:'Poppins', sans-serif; font-size:22px; font-weight:700; color:var(--navy); margin:0 0 4px; letter-spacing:-.4px; } .bf .cc-op-sub { font-size:14px; color:var(--muted); margin:0 0 20px; } .bf .cc-op-cards { display:grid; grid-template-columns:repeat(2,1fr); gap:14px; } .bf .cc-op-card { position:relative; text-align:left; border:1.5px solid var(--hair); border-radius:14px; padding:18px; cursor:pointer; background:#fff; width:100%; box-sizing:border-box; transition:border-color .2s, background .2s, box-shadow .2s; } .bf .cc-op-card:hover { border-color:var(--teal); box-shadow:0 4px 16px rgba(0,181,226,.12); } .bf .cc-op-card.is-active { border-color:var(--teal); background:rgba(0,181,226,.07); } .bf .cc-op-card-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; } .bf .cc-op-title { font-family:'Poppins', sans-serif; font-size:15.5px; font-weight:700; color:var(--navy); margin:0; line-height:1.25; } .bf .cc-op-badge { font-family:'Inter', sans-serif; font-size:11px; font-weight:700; padding:3px 10px; border-radius:999px; white-space:nowrap; flex-shrink:0; } .bf .cc-op-badge.is-reco { background:var(--teal); color:#fff; } .bf .cc-op-badge.is-valid { background:var(--sea-mist); color:var(--navy); border:1px solid var(--hair); } .bf .cc-op-text { font-size:13px; color:var(--body); line-height:1.55; margin:0 0 10px; } .bf .cc-op-mention { font-size:12px; color:var(--muted); font-style:italic; margin:0; } /* Sous-section admissibilité opérateur */ .bf .cc-elig { margin-top:18px; padding-top:18px; border-top:1px dashed var(--hair); } .bf .cc-elig h3 { font-family:'Poppins', sans-serif; font-size:16px; font-weight:700; color:var(--navy); margin:0 0 8px; } .bf .cc-elig-intro { font-size:13px; color:var(--body); line-height:1.6; margin:0 0 16px; } .bf .cc-elig-list { display:flex; flex-direction:column; gap:10px; margin-bottom:18px; } .bf .cc-elig-check { display:flex; align-items:flex-start; gap:10px; font-size:13.5px; color:var(--navy); line-height:1.45; cursor:pointer; } .bf .cc-elig-check input { width:18px; height:18px; margin-top:1px; accent-color:var(--teal); cursor:pointer; flex-shrink:0; } .bf .cc-fields { display:grid; grid-template-columns:1fr 1fr; gap:14px; } .bf .cc-field { display:flex; flex-direction:column; gap:5px; } .bf .cc-field.is-full { grid-column:1 / -1; } .bf .cc-field label { font-size:12.5px; font-weight:600; color:var(--navy); } .bf .cc-field input, .bf .cc-field textarea { font-family:'Inter', sans-serif; font-size:14px; color:var(--navy); border:1.5px solid var(--hair); border-radius:10px; padding:10px 12px; background:#fff; width:100%; box-sizing:border-box; } .bf .cc-field input:focus, .bf .cc-field textarea:focus { outline:none; border-color:var(--teal); } .bf .cc-field textarea { resize:vertical; min-height:64px; } .bf .cc-uploads { display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-top:14px; } .bf .cc-upload { border:1.5px dashed var(--hair); border-radius:12px; padding:18px 14px; text-align:center; color:var(--muted); font-size:13px; background:var(--sea-mist); line-height:1.4; } .bf .cc-upload .cc-upload-ic { font-size:22px; display:block; margin-bottom:6px; } .bf .cc-upload .cc-upload-soon { display:inline-block; margin-top:8px; font-size:11px; font-weight:700; color:var(--navy); background:#fff; border:1px solid var(--hair); border-radius:999px; padding:2px 8px; } /* Note légale + avertissement */ .bf .cc-op-legal { margin-top:18px; background:var(--sea-mist); border-left:3px solid var(--navy); border-radius:10px; padding:14px 16px; font-size:12.5px; color:var(--body); line-height:1.6; } .bf .cc-op-warning { margin-top:14px; display:flex; align-items:flex-start; gap:10px; background:#fff; border:1.5px solid var(--orange); border-radius:10px; padding:14px 16px; font-size:13.5px; color:var(--navy); font-weight:600; line-height:1.5; } .bf .cc-op-warning .cc-warn-ic { flex-shrink:0; font-size:16px; line-height:1.3; } .bf .cc-cta-hint { margin-top:10px; font-size:12px; color:var(--muted); text-align:center; line-height:1.5; } @media (max-width: 860px) { .bf .cc-op-sec { padding:18px 18px; } } @media (max-width: 600px) { .bf .cc-op-cards { grid-template-columns:1fr; } .bf .cc-fields { grid-template-columns:1fr; } .bf .cc-uploads { grid-template-columns:1fr; } } /* ===== Vos informations (demande de réservation V1) ===== */ .bf .cc-info-sec { background:#fff; border:1px solid var(--hair); border-radius:18px; padding:24px 26px; } .bf .cc-info-sec h2 { font-family:'Poppins', sans-serif; font-size:22px; font-weight:700; color:var(--navy); margin:0 0 4px; letter-spacing:-.4px; } .bf .cc-req { color:var(--orange); } .bf .cc-field.is-error input, .bf .cc-field.is-error select, .bf .cc-field.is-error textarea { border-color:#E0322F; background:#FFF6F5; } .bf .cc-summary .ccs-estimate-note { margin:10px 0 0; font-size:11.5px; color:var(--muted); line-height:1.45; } .bf .cc-summary .ccs-cta.is-inactive { opacity:.6; box-shadow:none; } .bf .cc-summary .ccs-cta.is-inactive:hover { background:var(--orange); transform:none; box-shadow:none; } /* ===== Confirmation d'envoi ===== */ .bf .cc-success { text-align:center; max-width:640px; margin:0 auto; background:#fff; border:1px solid var(--hair); border-radius:22px; padding:48px 32px; box-shadow:0 20px 46px -32px rgba(0,34,68,.4); } .bf .cc-success-ic { width:72px; height:72px; border-radius:50%; background:rgba(0,181,226,.12); color:var(--teal); display:inline-flex; align-items:center; justify-content:center; font-size:34px; font-weight:800; margin-bottom:20px; } .bf .cc-success h2 { font-family:'Poppins', sans-serif; font-size:26px; color:var(--navy); margin:0 0 12px; letter-spacing:-.5px; } .bf .cc-success > p { font-size:15px; color:var(--body); line-height:1.6; margin:0 auto 26px; max-width:520px; } .bf .cc-success-recap { text-align:left; background:var(--sea-mist); border-radius:14px; padding:18px 20px; margin-bottom:26px; } .bf .cc-success-recap .sr-row { display:flex; justify-content:space-between; gap:16px; padding:8px 0; font-size:14px; color:var(--body); border-bottom:1px dashed var(--hair); } .bf .cc-success-recap .sr-row:last-child { border-bottom:none; } .bf .cc-success-recap .sr-row b { color:var(--navy); font-weight:600; text-align:right; } .bf .cc-success-actions { display:flex; gap:12px; justify-content:center; flex-wrap:wrap; } .bf .cc-success-actions a { display:inline-flex; align-items:center; justify-content:center; padding:13px 24px; border-radius:999px; font-family:'Inter', sans-serif; font-weight:600; font-size:14.5px; text-decoration:none; transition:transform .2s, background .2s, color .2s, box-shadow .2s; } .bf .cc-success-actions .is-primary { background:var(--orange); color:#fff; box-shadow:var(--cta-shadow-orange); } .bf .cc-success-actions .is-primary:hover { background:#FF9420; transform:translateY(-2px); } .bf .cc-success-actions .is-secondary { background:#fff; color:var(--navy); border:1.5px solid rgba(0,34,68,.22); } .bf .cc-success-actions .is-secondary:hover { background:var(--navy); color:#fff; } @media (max-width: 600px) { .bf .cc-info-sec { padding:18px 18px; } .bf .cc-success { padding:36px 20px; } } `; function BoatRow({ b }) { return (
{b.name}

{b.name}

{b.type} {b.pers} pers.
{b.region} · {b.sector}{b.marina ? ` · ${b.marina}` : ""}
{blockTotal(b).toLocaleString("fr-CA")} $
Bloc 4h · Dépôt {depositFor(b).toLocaleString("fr-CA")} $
); } function AddonCard({ addon, selected, onToggle }) { return ( ); } function BFPageCheckoutCombine() { // 1) Priorité URL params, fallback localStorage (cas redirection avec sélection vide) const url = useMemo(readParams, []); const fallback = useMemo(() => (url.slugs.length === 0 ? readFallback() : null), [url]); const [selectedServices, setSelectedServices] = useState([]); const toggleService = (id) => setSelectedServices(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); // Mode d'opération : "" (à choisir) | "captain" | "self" const [operationMode, setOperationMode] = useState(""); const [elig, setElig] = useState({}); const toggleElig = (k) => setElig(prev => ({ ...prev, [k]: !prev[k] })); const [certType, setCertType] = useState(""); const [expYears, setExpYears] = useState(""); const [expDesc, setExpDesc] = useState(""); const allEligChecked = ELIG_ITEMS.every(it => elig[it.k]); const selfNeedsCaptain = operationMode === "self" && !allEligChecked; // Mode d'opération valide : capitaine demandé, ou auto-opération avec tous les critères cochés. const operationReady = operationMode === "captain" || (operationMode === "self" && allEligChecked); // Réservation simple si ?boat=slug fourni, sinon réservation combinée (≥ 2 bateaux) const isSingle = !!url.boat; const slugs = isSingle ? [url.boat] : (url.slugs.length > 0 ? url.slugs : (fallback?.slugs || [])); const region = url.region || fallback?.ctx?.region || ""; const boats = slugs.map(bySlug).filter(Boolean); const totalPrice = boats.reduce((sum, b) => sum + blockTotal(b), 0); const totalDeposit = boats.reduce((sum, b) => sum + depositFor(b), 0); const navigationZones = Array.from(new Set(boats.map((b) => b.navigationZone).filter(Boolean))); const zones = commonZones(boats); const boatNotFound = isSingle && boats.length === 0; // ?boat= invalide const ready = isSingle ? boats.length >= 1 : boats.length >= 2; // 2) Détails de sortie éditables (pré-remplis depuis l'URL / la sélection) const [date, setDate] = useState(url.date || fallback?.ctx?.date || ""); const [timeSlot, setTimeSlot] = useState(url.timeSlot || fallback?.ctx?.timeSlot || ""); const [guests, setGuests] = useState(""); // 3) Informations client (requises pour envoyer la demande) const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [message, setMessage] = useState(""); const [triedSubmit, setTriedSubmit] = useState(false); const [submitted, setSubmitted] = useState(false); const maxGuests = boats.length ? (isSingle ? boats[0].pers : boats.reduce((s, b) => s + (b.pers || 0), 0)) : 0; const slotOptions = (isSingle && boats[0] && boats[0].timeSlots && boats[0].timeSlots.length) ? boats[0].timeSlots : (TIME_SLOTS || []); const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); const formValid = !!( firstName.trim() && lastName.trim() && emailValid && phone.trim() && date && timeSlot && String(guests).trim() && Number(guests) >= 1 ); // Envoi autorisé : mode d'opération valide ET informations client complètes. const canSubmit = operationReady && formValid; // V1 : regroupe toute la demande dans un objet propre. Aucun paiement, aucun // appel API. Branchement Zapier / email à faire plus tard (voir handoff). const handleReservationRequestSubmit = (e) => { if (e && e.preventDefault) e.preventDefault(); setTriedSubmit(true); if (!canSubmit) return; const reservationRequest = { typeDemande: isSingle ? "reservation-simple" : "reservation-combinee", bateau: isSingle ? { slug: boats[0].slug, nom: boats[0].name, type: boats[0].type, secteur: boats[0].sector, marina: boats[0].marina } : boats.map((b) => ({ slug: b.slug, nom: b.name, type: b.type, secteur: b.sector, marina: b.marina })), date, plageHoraire: timeSlot, nombrePersonnes: Number(guests), modeOperation: operationMode, // "captain" | "self" operateurAutonome: operationMode === "self" ? { criteres: { ...elig }, typeCertificat: certType, anneesExperience: expYears, descriptionExperience: expDesc } : null, servicesAdditionnels: selectedServices .map((id) => (SERVICES.find((s) => s.id === id) || {}).title) .filter(Boolean), client: { prenom: firstName.trim(), nom: lastName.trim(), email: email.trim(), telephone: phone.trim() }, message: message.trim(), totalEstime: totalPrice || null, depotEstime: totalDeposit || null, soumisLe: new Date().toISOString() }; // TODO (post-V1) : envoyer reservationRequest vers Zapier / email au lieu du console.log. console.log("[BoatsFun] Demande de réservation V1 :", reservationRequest); setSubmitted(true); if (typeof window !== "undefined" && window.scrollTo) { window.scrollTo({ top: 0, behavior: "smooth" }); } }; return (
{isSingle ? "Réservation" : "Réservation combinée"}

{isSingle ? "Réservez votre sortie" : "Vos bateaux pour une sortie coordonnée"}

{isSingle ? "Vérifiez les détails de votre sortie, ajoutez vos services sur demande et envoyez votre demande de réservation." : "Vous réservez plusieurs bateaux dans le même secteur pour une sortie coordonnée. Dépôt 100 % remboursable si l'un des propriétaires refuse."}

{submitted ? (

Demande de réservation envoyée

Votre demande de réservation a été envoyée. L'équipe BoatsFun vous contactera rapidement pour confirmer la disponibilité.

{isSingle ? "Bateau" : "Bateaux"}{isSingle ? (boats[0] && boats[0].name) : boats.map((b) => b.name).join(", ")}
Date{formatDate(date)}
Plage horaire{timeSlot || "—"}
Personnes{guests || "—"}
Mode d'opération{operationMode === "captain" ? "Capitaine demandé" : "Opération autonome — sur validation"}
Contact{firstName} {lastName}
Voir d'autres bateaux Retour à l'accueil
) : !ready ? (
{boatNotFound ? (

Bateau introuvable

Bateau introuvable. Retournez au catalogue pour choisir un bateau.

Retour au catalogue
) : (

Aucune sélection trouvée

Retournez au catalogue, activez le mode Combiner plusieurs bateaux et sélectionnez au moins 2 bateaux compatibles.

Retour au catalogue
)}
) : (
{/* Colonne gauche : bateaux + add-ons */}

{isSingle ? "Votre bateau" : `${boats.length} bateaux sélectionnés`}

{boats.map((b) => )}

Comment souhaitez-vous organiser votre sortie ?

Choisissez comment votre bateau sera opéré pendant la sortie.

{operationMode === "self" && (

Vérification d'admissibilité opérateur

Pour des raisons de sécurité, d'assurance et de conformité, les demandes sans capitaine sont sujettes à validation. BoatsFun peut refuser une demande autonome si les documents ou l'expérience fournis ne sont pas suffisants.

{ELIG_ITEMS.map(it => ( ))}
setCertType(e.target.value)} placeholder="Ex. CCEP, carte de compétence d'opérateur…" />
setExpYears(e.target.value)} placeholder="Ex. 3" />