"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { Notice } from "./ui"; import { IconHeart, IconCompare, IconShare } from "./icons"; import { useFavorites, useCompare } from "@/lib/favorites"; function minSlot() { return new Date(Date.now() + 3600000).toISOString().slice(0, 16); } function idemKey(listingId: string, body: string) { return `${listingId}-${body.length}-${Math.floor(Date.now() / 16384)}`; } /** Panneau d'action d'une annonce directe Toveli : contacter, demander une visite, signaler. Connexion requise pour écrire (retour automatique sur la fiche). */ export function ContactPanel({ listingId, uid, publisherLabel, publisherName }: { listingId: string; uid: string; publisherLabel: string; publisherName: string | null }) { const [me, setMe] = useState<{ id: string } | null | undefined>(undefined); const [mode, setMode] = useState<"idle" | "contact" | "visit" | "report">("idle"); const [body, setBody] = useState(""); const [slots, setSlots] = useState(["", "", ""]); const [note, setNote] = useState(""); const [reason, setReason] = useState("arnaque"); const [msg, setMsg] = useState(null); const [busy, setBusy] = useState(false); const fav = useFavorites(); const cmp = useCompare(); useEffect(() => { fetch("/api/me").then((r) => r.json()).then((d) => setMe(d.user ?? null)).catch(() => setMe(null)); }, []); const here = typeof window !== "undefined" ? window.location.pathname : `/annonce/${encodeURIComponent(uid)}`; const need = () => { if (me === null) { window.location.href = `/connexion?next=${encodeURIComponent(here)}`; return true; } return false; }; const toISO = (v: string) => { const d = new Date(v); return Number.isNaN(d.getTime()) ? null : d.toISOString(); }; const send = async () => { setBusy(true); setMsg(null); try { if (mode === "contact") { const r = await fetch(`/api/listings/${listingId}/inquiries`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ body, category: "INFO", idempotencyKey: idemKey(listingId, body) }) }); const j = await r.json(); if (!r.ok) throw new Error(j.error?.message ?? "Erreur"); setMsg("Message envoyé. Suivez la conversation dans Mes messages."); setBody(""); } if (mode === "visit") { const iso = slots.map(toISO).filter((x): x is string => !!x); if (!iso.length) throw new Error("Proposez au moins un créneau"); const r = await fetch(`/api/listings/${listingId}/viewings`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ slots: iso, note: note || undefined }) }); const j = await r.json(); if (!r.ok) throw new Error(j.error?.message ?? "Erreur"); setMsg("Demande de visite envoyée. Vous serez avisé par courriel de la confirmation."); } if (mode === "report") { const r = await fetch("/api/reports", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ targetType: "LISTING", targetId: listingId, reason, details: note || undefined }) }); const j = await r.json(); if (!r.ok) throw new Error(j.error?.message ?? "Erreur"); setMsg("Signalement transmis à l'équipe Toveli. Merci."); } setMode("idle"); } catch (e) { setMsg((e as Error).message); } finally { setBusy(false); } }; const share = async () => { const u = window.location.href; if (navigator.share) { try { await navigator.share({ url: u, title: document.title }); } catch { /* */ } } else { await navigator.clipboard.writeText(u); setMsg("Lien copié"); } }; return (

Publiée sur Toveli

{publisherLabel}{publisherName ? ` · ${publisherName}` : ""}

Les échanges passent par la messagerie Toveli : aucune coordonnée n'est affichée publiquement.

{me === null &&

Connexion par code courriel requise pour écrire au propriétaire (30 secondes). Se connecter

} {mode === "contact" &&