Disattiva temporaneamente form prenotazioni

This commit is contained in:
2026-08-21 19:02:16 +02:00
parent d9b617e706
commit 04fb49ad4b
6 changed files with 444 additions and 47 deletions

View File

@@ -2,12 +2,13 @@
* DESIGN: "Clinical Warmth"
* Sezione prenotazione: form to mail con conferma non vincolante.
*/
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { motion, useInView } from "framer-motion";
import {
Calendar,
CheckCircle2,
Clock,
Mail,
PawPrint,
Phone,
ShieldCheck,
@@ -17,6 +18,8 @@ import { toast } from "sonner";
import { Button } from "@/components/ui/button";
const bookingFormPaused = true;
const services = [
"Visita clinica generale",
"Ecografia",
@@ -45,29 +48,26 @@ const doctors = [
];
const openingHours = [
{ days: "Visite: Lunedi - Venerdi", hours: "09:00 - 19:30" },
{ days: "Visite: Lunedì - Venerdì", hours: "09:00 - 19:30" },
{ days: "Visite: Sabato", hours: "09:00 - 17:00" },
{ days: "Urgenze: Lunedi - Venerdi", hours: "08:00 - 22:30" },
{ days: "Urgenze: Lunedì - Venerdì", hours: "08:00 - 22:30" },
{ days: "Urgenze: Sabato e Festivi", hours: "09:00 - 20:00" },
];
const timeSlots = [
"09:00",
"09:30",
"10:00",
"10:30",
"11:00",
"11:30",
"14:30",
"15:00",
"15:30",
"16:00",
"16:30",
"17:00",
const preferredPeriods = [
"Prossima settimana",
"Tra 2 settimane",
"Il mese prossimo",
];
const preferredTimeWindows = [
"Mattino",
"Pomeriggio",
];
type BookingFormState = {
name: string;
email: string;
phone: string;
petName: string;
petType: string;
@@ -76,10 +76,13 @@ type BookingFormState = {
date: string;
time: string;
notes: string;
website: string;
captchaAnswer: string;
};
const initialForm: BookingFormState = {
name: "",
email: "",
phone: "",
petName: "",
petType: "cane",
@@ -88,6 +91,13 @@ const initialForm: BookingFormState = {
date: "",
time: "",
notes: "",
website: "",
captchaAnswer: "",
};
type CaptchaChallenge = {
captcha_id: string;
question: string;
};
export default function BookingSection() {
@@ -97,15 +107,51 @@ export default function BookingSection() {
const [submitting, setSubmitting] = useState(false);
const [submittedName, setSubmittedName] = useState("");
const [form, setForm] = useState<BookingFormState>(initialForm);
const [captcha, setCaptcha] = useState<CaptchaChallenge | null>(null);
const [captchaLoading, setCaptchaLoading] = useState(false);
const loadCaptcha = async () => {
setCaptchaLoading(true);
try {
const response = await fetch("/api/booking-captcha");
if (!response.ok) {
throw new Error("Verifica anti-spam non disponibile");
}
const payload = await response.json();
setCaptcha(payload);
setForm((currentForm) => ({ ...currentForm, captchaAnswer: "" }));
} catch {
setCaptcha(null);
toast.error("Verifica anti-spam non disponibile", {
description: "Riprova tra qualche istante prima di inviare il modulo.",
});
} finally {
setCaptchaLoading(false);
}
};
useEffect(() => {
if (!bookingFormPaused) {
loadCaptcha();
}
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!form.name || !form.phone || !form.doctor || !form.service || !form.date) {
if (!form.name || !form.email || !form.phone || !form.doctor || !form.service || !form.date) {
toast.error("Compila tutti i campi obbligatori");
return;
}
if (!captcha || !form.captchaAnswer) {
toast.error("Completa la verifica anti-spam");
return;
}
try {
setSubmitting(true);
@@ -116,6 +162,7 @@ export default function BookingSection() {
},
body: JSON.stringify({
name: form.name,
email: form.email,
phone: form.phone,
pet_name: form.petName,
pet_type: form.petType,
@@ -124,6 +171,9 @@ export default function BookingSection() {
date: form.date,
time: form.time,
notes: form.notes,
website: form.website,
captcha_id: captcha.captcha_id,
captcha_answer: form.captchaAnswer,
}),
});
@@ -137,10 +187,13 @@ export default function BookingSection() {
setSubmittedName(form.name);
setSubmitted(true);
setForm(initialForm);
loadCaptcha();
toast.success("Richiesta inviata", {
description: payload.message,
});
} catch (error) {
loadCaptcha();
const message =
error instanceof Error
? error.message
@@ -189,13 +242,13 @@ export default function BookingSection() {
<p className="mb-8 text-base leading-relaxed text-white/80">
Compila il modulo per inviare una richiesta di appuntamento. La richiesta
non e vincolante e dovra essere confermata dallo staff della clinica, che ti
ricontattera il prima possibile.
non è vincolante e dovrà essere confermata dallo staff della clinica, che ti
ricontatterà il prima possibile.
</p>
<div className="mb-8 rounded-xl border border-white/20 bg-white/10 p-5 backdrop-blur-sm">
<p className="mb-2 text-sm font-semibold uppercase tracking-wide text-[#4ECDC4]">
Urgenze 24h
Urgenze
</p>
<a
href="tel:3205322439"
@@ -204,7 +257,6 @@ export default function BookingSection() {
>
320 532.24.39
</a>
<p className="mt-1 text-xs text-white/60">Disponibile 7 giorni su 7</p>
</div>
<div className="mb-8 rounded-2xl border border-[#4ECDC4]/30 bg-[#14384F] p-5">
@@ -216,8 +268,8 @@ export default function BookingSection() {
<p className="text-sm font-semibold text-white">Richiesta non vincolante</p>
<p className="mt-1 text-sm leading-relaxed text-white/75">
L&apos;invio del modulo non costituisce conferma automatica dell&apos;appuntamento.
Il team verifichera disponibilita, tipologia di visita e urgenza del caso prima
di confermare data e orario.
Il team verificherà disponibilità, tipologia di visita e urgenza del caso prima
di confermare periodo e fascia oraria.
</p>
</div>
</div>
@@ -263,7 +315,29 @@ export default function BookingSection() {
animate={isInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2 }}
>
{submitted ? (
{bookingFormPaused ? (
<div className="rounded-2xl bg-white p-8 text-center shadow-2xl">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[#4ECDC4]/15">
<Clock size={32} className="text-[#4ECDC4]" />
</div>
<h3
className="mb-3 text-[#1B4F72]"
style={{ fontFamily: "'Cormorant Garamond', serif", fontSize: "1.9rem" }}
>
Form in aggiornamento
</h3>
<p className="mb-6 text-sm leading-relaxed text-gray-600">
Il modulo di richiesta appuntamento è temporaneamente sospeso per manutenzione.
Per prenotazioni o informazioni contatta direttamente la clinica.
</p>
<a
href="tel:3205322439"
className="inline-flex items-center justify-center rounded-lg bg-[#1B4F72] px-5 py-3 text-sm font-bold text-white transition-colors hover:bg-[#163d5a]"
>
Chiama 320 532.24.39
</a>
</div>
) : submitted ? (
<div className="rounded-2xl bg-white p-8 text-center shadow-2xl">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-[#4ECDC4]/15">
<CheckCircle2 size={32} className="text-[#4ECDC4]" />
@@ -275,11 +349,11 @@ export default function BookingSection() {
Grazie {submittedName || "per la tua richiesta"}
</h3>
<p className="mb-3 text-sm leading-relaxed text-gray-600">
La tua richiesta di prenotazione e stata inviata correttamente.
La tua richiesta di prenotazione è stata inviata correttamente.
</p>
<p className="mb-6 text-sm leading-relaxed text-gray-600">
Il team della Clinica Veterinaria Formiginese la prendera in carico il prima
possibile e ti ricontattera per confermare disponibilita, data e orario.
Il team della Clinica Veterinaria Formiginese la prenderà in carico il prima
possibile e ti ricontatterà per confermare disponibilità, data e orario.
</p>
<div className="mb-6 rounded-xl border border-[#E4D7C6] bg-[#FFF9F1] px-4 py-3 text-left text-sm text-gray-600">
<strong className="text-[#1B4F72]">Nota importante:</strong> la richiesta inviata
@@ -309,8 +383,8 @@ export default function BookingSection() {
Richiedi un appuntamento
</h3>
<p className="text-sm leading-relaxed text-gray-500">
Compila i campi richiesti e inviaci una proposta di data: sara lo staff a
confermare la visita.
Compila i campi richiesti e inviaci una preferenza di periodo: sarà lo staff a
ricontattarti per confermare la visita.
</p>
</div>
@@ -332,6 +406,23 @@ export default function BookingSection() {
</div>
</div>
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
Email *
</label>
<div className="relative">
<Mail size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input
type="email"
required
placeholder="nome@email.it"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="w-full rounded-lg border border-gray-200 py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
/>
</div>
</div>
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
Telefono *
@@ -424,24 +515,29 @@ export default function BookingSection() {
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
Data preferita *
Periodo preferito *
</label>
<div className="relative">
<Calendar size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input
type="date"
<select
required
value={form.date}
min={new Date().toISOString().split("T")[0]}
onChange={(e) => setForm({ ...form, date: e.target.value })}
className="w-full rounded-lg border border-gray-200 py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
/>
className="w-full rounded-lg border border-gray-200 bg-white py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
>
<option value="">Seleziona un periodo</option>
{preferredPeriods.map((period) => (
<option key={period} value={period}>
{period}
</option>
))}
</select>
</div>
</div>
<div>
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
Orario preferito
Fascia oraria preferita
</label>
<div className="relative">
<Clock size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
@@ -450,8 +546,8 @@ export default function BookingSection() {
onChange={(e) => setForm({ ...form, time: e.target.value })}
className="w-full rounded-lg border border-gray-200 bg-white py-2.5 pl-9 pr-3 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4]"
>
<option value="">Qualsiasi orario</option>
{timeSlots.map((time) => (
<option value="">Seleziona una fascia oraria</option>
{preferredTimeWindows.map((time) => (
<option key={time} value={time}>
{time}
</option>
@@ -474,16 +570,58 @@ export default function BookingSection() {
/>
</div>
<div className="hidden" aria-hidden="true">
<label>
Sito web
<input
type="text"
tabIndex={-1}
autoComplete="off"
value={form.website}
onChange={(e) => setForm({ ...form, website: e.target.value })}
/>
</label>
</div>
<div className="rounded-xl border border-gray-200 bg-gray-50 px-4 py-3">
<label className="mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-600">
Verifica anti-spam *
</label>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<span className="text-sm font-semibold text-[#1B4F72]">
{captchaLoading ? "Caricamento..." : captcha?.question || "Verifica non disponibile"}
</span>
<input
type="text"
inputMode="numeric"
required
placeholder="Risposta"
value={form.captchaAnswer}
onChange={(e) => setForm({ ...form, captchaAnswer: e.target.value })}
disabled={!captcha || captchaLoading}
className="w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#4ECDC4] disabled:cursor-not-allowed disabled:bg-gray-100 sm:max-w-32"
/>
<button
type="button"
onClick={loadCaptcha}
disabled={captchaLoading || submitting}
className="text-left text-xs font-semibold uppercase tracking-wide text-[#1B4F72] transition-colors hover:text-[#4ECDC4] disabled:cursor-wait disabled:opacity-60 sm:text-center"
>
Ricarica
</button>
</div>
</div>
<Button
type="submit"
disabled={submitting}
disabled={submitting || captchaLoading || !captcha}
className="w-full bg-[#4ECDC4] py-3 text-base font-bold text-white shadow-lg shadow-[#4ECDC4]/30 transition-all duration-300 hover:bg-[#3ab5ad] hover:shadow-xl disabled:cursor-wait disabled:opacity-80"
>
{submitting ? "Invio in corso..." : "Invia richiesta di prenotazione"}
</Button>
<p className="text-center text-xs text-gray-500">
* Campi obbligatori. La richiesta sara valutata e confermata dallo staff.
* Campi obbligatori. La richiesta sarà valutata e confermata dallo staff.
</p>
</form>
)}