diff --git a/.gitignore b/.gitignore index befef02..7095590 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ # Local environment files .env backend/.env +perl_mail.txt # Local SQLite databases data/*.db diff --git a/assets/news_consulenza.jpg b/assets/news_consulenza.jpg new file mode 100644 index 0000000..1af533e Binary files /dev/null and b/assets/news_consulenza.jpg differ diff --git a/assets/news_cucciolo.jpg b/assets/news_cucciolo.jpg new file mode 100644 index 0000000..fe3118d Binary files /dev/null and b/assets/news_cucciolo.jpg differ diff --git a/assets/news_gatto.jpg b/assets/news_gatto.jpg new file mode 100644 index 0000000..6984665 Binary files /dev/null and b/assets/news_gatto.jpg differ diff --git a/assets/services_dermatologia.jpg b/assets/services_dermatologia.jpg new file mode 100644 index 0000000..491bc78 Binary files /dev/null and b/assets/services_dermatologia.jpg differ diff --git a/assets/services_ecografia.jpg b/assets/services_ecografia.jpg new file mode 100644 index 0000000..ec590a9 Binary files /dev/null and b/assets/services_ecografia.jpg differ diff --git a/assets/services_endoscopia.jpg b/assets/services_endoscopia.jpg new file mode 100644 index 0000000..9173728 Binary files /dev/null and b/assets/services_endoscopia.jpg differ diff --git a/assets/services_endoscopia.webp b/assets/services_endoscopia.webp new file mode 100644 index 0000000..f1181e6 Binary files /dev/null and b/assets/services_endoscopia.webp differ diff --git a/assets/services_laboratorio.jpg b/assets/services_laboratorio.jpg new file mode 100644 index 0000000..4fbf811 Binary files /dev/null and b/assets/services_laboratorio.jpg differ diff --git a/assets/services_laparoscopia.jpg b/assets/services_laparoscopia.jpg new file mode 100644 index 0000000..8557e84 Binary files /dev/null and b/assets/services_laparoscopia.jpg differ diff --git a/assets/services_oculistica.jpg b/assets/services_oculistica.jpg new file mode 100644 index 0000000..db4db22 Binary files /dev/null and b/assets/services_oculistica.jpg differ diff --git a/assets/services_radiologia.jpg b/assets/services_radiologia.jpg new file mode 100644 index 0000000..c26d8f5 Binary files /dev/null and b/assets/services_radiologia.jpg differ diff --git a/assets/services_visite_cliniche.jpg b/assets/services_visite_cliniche.jpg new file mode 100644 index 0000000..d60e896 Binary files /dev/null and b/assets/services_visite_cliniche.jpg differ diff --git a/backend/.env.example b/backend/.env.example index 0baa0f7..c2e92bd 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -3,3 +3,11 @@ APP_ENV=development APP_HOST=127.0.0.1 APP_PORT=8000 DATABASE_URL=sqlite:///C:/devel/clinica_veterinaria_formiginese/data/clinica.db +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_USE_TLS=true +BOOKING_EMAIL_FROM= +BOOKING_EMAIL_TO= +BOOKING_EMAIL_SUBJECT_PREFIX=[Clinica Veterinaria Formiginese] diff --git a/backend/app/api/booking.py b/backend/app/api/booking.py new file mode 100644 index 0000000..c2d722b --- /dev/null +++ b/backend/app/api/booking.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, HTTPException, status + +from app.schemas.booking import BookingRequestCreate, BookingRequestResponse +from app.services.mailer import MailConfigurationError, send_booking_request_email + + +router = APIRouter(prefix="/api", tags=["booking"]) + + +@router.post("/booking-request", response_model=BookingRequestResponse) +def create_booking_request(payload: BookingRequestCreate) -> BookingRequestResponse: + try: + send_booking_request_email(payload) + except MailConfigurationError as exc: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Il servizio email non è ancora configurato.", + ) from exc + except Exception as exc: # pragma: no cover - mail delivery path + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Si è verificato un problema durante l'invio della richiesta.", + ) from exc + + return BookingRequestResponse( + message="La richiesta è stata inviata correttamente e sarà presa in carico al più presto dal team.", + ) diff --git a/backend/app/config.py b/backend/app/config.py index bfa3480..e131a89 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -6,9 +6,11 @@ from dotenv import load_dotenv BASE_DIR = Path(__file__).resolve().parent.parent PROJECT_ROOT = BASE_DIR.parent -ENV_FILE = PROJECT_ROOT / ".env" +ENV_FILE = BASE_DIR / ".env" +ROOT_ENV_FILE = PROJECT_ROOT / ".env" -load_dotenv(ENV_FILE) +load_dotenv(ROOT_ENV_FILE) +load_dotenv(ENV_FILE, override=True) class Settings: @@ -19,6 +21,17 @@ class Settings: self.app_port = int(os.getenv("APP_PORT", "8000")) default_sqlite_path = (PROJECT_ROOT / "data" / "clinica.db").resolve() self.database_url = os.getenv("DATABASE_URL", f"sqlite:///{default_sqlite_path.as_posix()}") + self.smtp_host = os.getenv("SMTP_HOST", "smtp.gmail.com") + self.smtp_port = int(os.getenv("SMTP_PORT", "587")) + self.smtp_username = os.getenv("SMTP_USERNAME", "") + self.smtp_password = os.getenv("SMTP_PASSWORD", "") + self.smtp_use_tls = os.getenv("SMTP_USE_TLS", "true").lower() in {"1", "true", "yes", "on"} + self.booking_email_from = os.getenv("BOOKING_EMAIL_FROM", self.smtp_username) + self.booking_email_to = os.getenv("BOOKING_EMAIL_TO", "") + self.booking_email_subject_prefix = os.getenv( + "BOOKING_EMAIL_SUBJECT_PREFIX", + "[Clinica Veterinaria Formiginese]", + ) @property def sqlite_file_path(self) -> str | None: @@ -27,5 +40,18 @@ class Settings: return self.database_url.removeprefix(sqlite_prefix) return None + @property + def booking_mail_enabled(self) -> bool: + return all( + [ + self.smtp_host, + self.smtp_port, + self.smtp_username, + self.smtp_password, + self.booking_email_from, + self.booking_email_to, + ] + ) + settings = Settings() diff --git a/backend/app/main.py b/backend/app/main.py index 5ad0c70..f611b3c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI +from app.api.booking import router as booking_router from app.api.health import router as health_router from app.config import settings from app.db.init_db import init_db @@ -9,3 +10,4 @@ init_db() app = FastAPI(title=settings.app_name) app.include_router(health_router) +app.include_router(booking_router) diff --git a/backend/app/schemas/booking.py b/backend/app/schemas/booking.py new file mode 100644 index 0000000..b4d6a14 --- /dev/null +++ b/backend/app/schemas/booking.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, Field + + +class BookingRequestCreate(BaseModel): + name: str = Field(min_length=2, max_length=120) + phone: str = Field(min_length=5, max_length=40) + pet_name: str = Field(default="", max_length=120) + pet_type: str = Field(default="cane", max_length=40) + service: str = Field(min_length=2, max_length=120) + date: str = Field(min_length=8, max_length=20) + time: str = Field(default="", max_length=20) + notes: str = Field(default="", max_length=2000) + + +class BookingRequestResponse(BaseModel): + success: bool = True + message: str diff --git a/backend/app/services/mailer.py b/backend/app/services/mailer.py new file mode 100644 index 0000000..ad3c9fc --- /dev/null +++ b/backend/app/services/mailer.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from datetime import datetime +from email.message import EmailMessage +import smtplib + +from app.config import settings +from app.schemas.booking import BookingRequestCreate + + +class MailConfigurationError(RuntimeError): + pass + + +def _build_booking_subject(payload: BookingRequestCreate) -> str: + return f"{settings.booking_email_subject_prefix} Nuova richiesta visita - {payload.name}" + + +def _build_booking_html(payload: BookingRequestCreate) -> str: + submitted_at = datetime.now().strftime("%d/%m/%Y %H:%M") + return f""" + + +

Nuova richiesta di prenotazione visita

+

È stata inviata una nuova richiesta di prenotazione non vincolante dal sito web.

+ + + + + + + + + + +
Nome e cognome{payload.name}
Telefono{payload.phone}
Nome animale{payload.pet_name or "-"}
Tipo animale{payload.pet_type}
Tipo di visita{payload.service}
Data preferita{payload.date}
Orario preferito{payload.time or "Qualsiasi orario"}
Note{payload.notes or "-"}
Inviata il{submitted_at}
+

+ Questa richiesta non costituisce conferma automatica dell'appuntamento e richiede + presa in carico da parte dello staff. +

+ + + """ + + +def _build_booking_text(payload: BookingRequestCreate) -> str: + submitted_at = datetime.now().strftime("%d/%m/%Y %H:%M") + return ( + "Nuova richiesta di prenotazione visita\n\n" + f"Nome e cognome: {payload.name}\n" + f"Telefono: {payload.phone}\n" + f"Nome animale: {payload.pet_name or '-'}\n" + f"Tipo animale: {payload.pet_type}\n" + f"Tipo di visita: {payload.service}\n" + f"Data preferita: {payload.date}\n" + f"Orario preferito: {payload.time or 'Qualsiasi orario'}\n" + f"Note: {payload.notes or '-'}\n" + f"Inviata il: {submitted_at}\n\n" + "La richiesta non costituisce conferma automatica dell'appuntamento." + ) + + +def send_booking_request_email(payload: BookingRequestCreate) -> None: + if not settings.booking_mail_enabled: + raise MailConfigurationError("Configurazione SMTP incompleta.") + + message = EmailMessage() + message["Subject"] = _build_booking_subject(payload) + message["From"] = settings.booking_email_from + message["To"] = settings.booking_email_to + message.set_content(_build_booking_text(payload)) + message.add_alternative(_build_booking_html(payload), subtype="html") + + with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=30) as server: + if settings.smtp_use_tls: + server.starttls() + server.login(settings.smtp_username, settings.smtp_password) + server.send_message(message) diff --git a/backend/requirements.txt b/backend/requirements.txt index b188404..69abdff 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,3 +4,4 @@ sqlalchemy pydantic python-dotenv aiosqlite +pydantic[email] diff --git a/clinica-app/client/index.html b/clinica-app/client/index.html index cebd12e..284fd16 100644 --- a/clinica-app/client/index.html +++ b/clinica-app/client/index.html @@ -12,9 +12,5 @@
- diff --git a/clinica-app/client/public/images/news_consulenza.jpg b/clinica-app/client/public/images/news_consulenza.jpg new file mode 100644 index 0000000..1af533e Binary files /dev/null and b/clinica-app/client/public/images/news_consulenza.jpg differ diff --git a/clinica-app/client/public/images/news_cucciolo.jpg b/clinica-app/client/public/images/news_cucciolo.jpg new file mode 100644 index 0000000..fe3118d Binary files /dev/null and b/clinica-app/client/public/images/news_cucciolo.jpg differ diff --git a/clinica-app/client/public/images/news_gatto.jpg b/clinica-app/client/public/images/news_gatto.jpg new file mode 100644 index 0000000..6984665 Binary files /dev/null and b/clinica-app/client/public/images/news_gatto.jpg differ diff --git a/clinica-app/client/public/images/services_dermatologia.jpg b/clinica-app/client/public/images/services_dermatologia.jpg new file mode 100644 index 0000000..491bc78 Binary files /dev/null and b/clinica-app/client/public/images/services_dermatologia.jpg differ diff --git a/clinica-app/client/public/images/services_ecografia.jpg b/clinica-app/client/public/images/services_ecografia.jpg new file mode 100644 index 0000000..ec590a9 Binary files /dev/null and b/clinica-app/client/public/images/services_ecografia.jpg differ diff --git a/clinica-app/client/public/images/services_endoscopia.jpg b/clinica-app/client/public/images/services_endoscopia.jpg new file mode 100644 index 0000000..9173728 Binary files /dev/null and b/clinica-app/client/public/images/services_endoscopia.jpg differ diff --git a/clinica-app/client/public/images/services_endoscopia.webp b/clinica-app/client/public/images/services_endoscopia.webp new file mode 100644 index 0000000..f1181e6 Binary files /dev/null and b/clinica-app/client/public/images/services_endoscopia.webp differ diff --git a/clinica-app/client/public/images/services_laboratorio.jpg b/clinica-app/client/public/images/services_laboratorio.jpg new file mode 100644 index 0000000..4fbf811 Binary files /dev/null and b/clinica-app/client/public/images/services_laboratorio.jpg differ diff --git a/clinica-app/client/public/images/services_laparoscopia.jpg b/clinica-app/client/public/images/services_laparoscopia.jpg new file mode 100644 index 0000000..8557e84 Binary files /dev/null and b/clinica-app/client/public/images/services_laparoscopia.jpg differ diff --git a/clinica-app/client/public/images/services_oculistica.jpg b/clinica-app/client/public/images/services_oculistica.jpg new file mode 100644 index 0000000..db4db22 Binary files /dev/null and b/clinica-app/client/public/images/services_oculistica.jpg differ diff --git a/clinica-app/client/public/images/services_radiologia.jpg b/clinica-app/client/public/images/services_radiologia.jpg new file mode 100644 index 0000000..c26d8f5 Binary files /dev/null and b/clinica-app/client/public/images/services_radiologia.jpg differ diff --git a/clinica-app/client/public/images/services_visite_cliniche.jpg b/clinica-app/client/public/images/services_visite_cliniche.jpg new file mode 100644 index 0000000..d60e896 Binary files /dev/null and b/clinica-app/client/public/images/services_visite_cliniche.jpg differ diff --git a/clinica-app/client/src/App.tsx b/clinica-app/client/src/App.tsx index c698e71..9a344b9 100644 --- a/clinica-app/client/src/App.tsx +++ b/clinica-app/client/src/App.tsx @@ -1,6 +1,9 @@ import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; +import CookiePolicyPage from "@/pages/CookiePolicyPage"; +import LegalNotesPage from "@/pages/LegalNotesPage"; import NotFound from "@/pages/NotFound"; +import PrivacyPolicyPage from "@/pages/PrivacyPolicyPage"; import { Route, Switch } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; @@ -10,6 +13,9 @@ function Router() { return ( + + + diff --git a/clinica-app/client/src/components/AboutSection.tsx b/clinica-app/client/src/components/AboutSection.tsx index c6b0815..59c4dc7 100644 --- a/clinica-app/client/src/components/AboutSection.tsx +++ b/clinica-app/client/src/components/AboutSection.tsx @@ -97,7 +97,7 @@ export default function AboutSection() { {/* Immagine principale */}
Cane e gatto insieme — la nostra missione diff --git a/clinica-app/client/src/components/AuthSection.tsx b/clinica-app/client/src/components/AuthSection.tsx index 3009677..c7ed8c9 100644 --- a/clinica-app/client/src/components/AuthSection.tsx +++ b/clinica-app/client/src/components/AuthSection.tsx @@ -1,65 +1,76 @@ /* * DESIGN: "Clinical Warmth" - * Sezione registrazione/login: tabs con form eleganti - * Sfondo bianco, accenti blu petrolio e verde acqua + * Sezione registrazione/login visibile ma non ancora attiva. + * Gli elementi restano leggibili, ma ogni interazione mostra + * il messaggio "Servizio in corso di attivazione". */ -import { useState } from "react"; -import { motion } from "framer-motion"; -import { useInView } from "framer-motion"; +import { motion, useInView } from "framer-motion"; import { useRef } from "react"; import { Button } from "@/components/ui/button"; -import { User, Mail, Lock, Eye, EyeOff, CheckCircle2, PawPrint } from "lucide-react"; +import { User, Mail, Lock, Eye, CheckCircle2, PawPrint, Clock3 } from "lucide-react"; import { toast } from "sonner"; +const benefits = [ + "Storico visite e referti digitali", + "Promemoria vaccinazioni automatici", + "Prenotazioni online prioritarie", + "Comunicazioni dirette con il veterinario", + "Gestione di piu animali domestici", +]; + +function showActivationToast() { + toast.info("Servizio in corso di attivazione", { + description: "L'area riservata sara disponibile a breve.", + }); +} + +function DisabledInput({ + icon, + type, + placeholder, +}: { + icon: React.ComponentType<{ size?: number; className?: string }>; + type: string; + placeholder: string; +}) { + const Icon = icon; + + return ( +
+ + +
+ ); +} + export default function AuthSection() { const ref = useRef(null); const isInView = useInView(ref, { once: true, margin: "-80px" }); - const [tab, setTab] = useState<"login" | "register">("register"); - const [showPassword, setShowPassword] = useState(false); - const [registered, setRegistered] = useState(false); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (tab === "register") { - setRegistered(true); - toast.success("Registrazione completata!", { - description: "Benvenuto nella Clinica Veterinaria Formiginese.", - }); - } else { - toast.success("Accesso effettuato!", { - description: "Bentornato nella tua area personale.", - }); - } - }; - - const benefits = [ - "Storico visite e referti digitali", - "Promemoria vaccinazioni automatici", - "Prenotazioni online prioritarie", - "Comunicazioni dirette con il veterinario", - "Gestione di più animali domestici", - ]; return ( -
+
-
- {/* Colonna sinistra: benefici */} +
-
-
- +
+
+ Area Personale

- Registrati e gestisci{" "} + Accedi e gestisci{" "} la salute del tuo animale

-

- Crea il tuo profilo personale per accedere a tutti i servizi digitali della clinica. - Tieni traccia della storia clinica del tuo animale, ricevi promemoria e prenota - le visite in pochi click. +

+ L'area riservata e la registrazione online sono in fase di attivazione. + Qui i clienti potranno gestire i propri dati, consultare lo storico e accedere + ai servizi digitali della clinica in uno spazio personale dedicato.

- {/* Benefits list */} -
+
{benefits.map((benefit) => (
-
+
- {benefit} + {benefit}
))}
- {/* Decorazione */} -
-
+
+
-

Già più di 500 famiglie

-

- si affidano alla nostra clinica per la cura dei loro animali +

Servizio in attivazione

+

+ La clinica sta completando la configurazione dell'area riservata per offrirti + un accesso semplice e sicuro.

- {/* Colonna destra: form */} - {registered ? ( -
-
- -
-

+
+

-

- La tua registrazione è avvenuta con successo. Ora puoi accedere a tutti i servizi - della tua area personale. -

- + + Accedi +
- ) : ( -
- {/* Tabs */} -
- {(["register", "login"] as const).map((t) => ( - - ))} + +
+
+
+ +
+
+

Servizio in corso di attivazione

+

+ Registrazione e accesso saranno disponibili a breve. +

+
-
- {tab === "register" && ( -
-
- -
- - -
-
-
- -
- - -
-
+
+
+
+ +
- )} - -
- -
- - +
+ +
-
+ +
+
- + + +
- {tab === "register" && ( -
- - -
- )} +
+ +

+ Accetto la Privacy Policy e i Termini di Servizio. +

+
- {tab === "login" && ( -

- -

- )} - +

Password dimenticata?

+
- )} + +
diff --git a/clinica-app/client/src/components/BookingSection.tsx b/clinica-app/client/src/components/BookingSection.tsx index 847cbdd..968dc48 100644 --- a/clinica-app/client/src/components/BookingSection.tsx +++ b/clinica-app/client/src/components/BookingSection.tsx @@ -1,68 +1,132 @@ /* * DESIGN: "Clinical Warmth" - * Sezione prenotazione: form elegante su sfondo blu petrolio - * Layout: testo a sinistra + form a destra + * Sezione prenotazione: form to mail con conferma non vincolante. */ import { useState } from "react"; -import { motion } from "framer-motion"; -import { useInView } from "framer-motion"; +import { motion, useInView } from "framer-motion"; import { useRef } from "react"; import { Button } from "@/components/ui/button"; -import { Calendar, Clock, User, Phone, PawPrint, CheckCircle2 } from "lucide-react"; +import { Calendar, Clock, User, Phone, PawPrint, CheckCircle2, ShieldCheck } from "lucide-react"; import { toast } from "sonner"; const services = [ - "Visita generale", - "Radiologia / Ecografia", - "Chirurgia (consulenza)", - "Laboratorio analisi", + "Visita clinica generale", + "Ecografia", + "Radiologia", + "Laboratorio", "Vaccinazione", - "Dermatologia", - "Odontoiatria", "Oncologia", + "Dermatologia", + "Oculistica", + "Nutrizione", + "Ortopedia", + "Endoscopia", + "Laparoscopia", ]; 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", + "09:00", + "09:30", + "10:00", + "10:30", + "11:00", + "11:30", + "14:30", + "15:00", + "15:30", + "16:00", + "16:30", + "17:00", ]; +type BookingFormState = { + name: string; + phone: string; + petName: string; + petType: string; + service: string; + date: string; + time: string; + notes: string; +}; + +const initialForm: BookingFormState = { + name: "", + phone: "", + petName: "", + petType: "cane", + service: "", + date: "", + time: "", + notes: "", +}; + export default function BookingSection() { const ref = useRef(null); const isInView = useInView(ref, { once: true, margin: "-80px" }); const [submitted, setSubmitted] = useState(false); - const [form, setForm] = useState({ - name: "", - phone: "", - petName: "", - petType: "cane", - service: "", - date: "", - time: "", - notes: "", - }); + const [submitting, setSubmitting] = useState(false); + const [submittedName, setSubmittedName] = useState(""); + const [form, setForm] = useState(initialForm); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (!form.name || !form.phone || !form.service || !form.date) { toast.error("Compila tutti i campi obbligatori"); return; } - setSubmitted(true); - toast.success("Richiesta inviata!", { - description: "Ti contatteremo entro 24 ore per confermare l'appuntamento.", - }); + + try { + setSubmitting(true); + + const response = await fetch("/api/booking-request", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: form.name, + phone: form.phone, + pet_name: form.petName, + pet_type: form.petType, + service: form.service, + date: form.date, + time: form.time, + notes: form.notes, + }), + }); + + if (!response.ok) { + const errorPayload = await response.json().catch(() => null); + throw new Error(errorPayload?.detail || "Invio non riuscito"); + } + + const payload = await response.json(); + + setSubmittedName(form.name); + setSubmitted(true); + setForm(initialForm); + toast.success("Richiesta inviata", { + description: payload.message, + }); + } catch (error) { + const message = error instanceof Error ? error.message : "Si è verificato un problema durante l'invio."; + toast.error("Invio non riuscito", { + description: message, + }); + } finally { + setSubmitting(false); + } }; return ( -
- {/* Decorazioni di sfondo */} -
-
+
+
+
-
- {/* Colonna sinistra: testo */} +
-
-
- +
+
+ Prenotazioni

- Prenota la tua visita{" "} + Richiedi una visita{" "} online

-

- Compila il modulo per richiedere un appuntamento. Ti contatteremo entro 24 ore - per confermare la data e l'orario. Per urgenze, chiama direttamente il numero - dedicato disponibile 24 ore su 24. +

+ Compila il modulo per inviare una richiesta di appuntamento. La richiesta + non è vincolante e dovrà essere confermata dallo staff della clinica, che ti + ricontatterà il prima possibile.

- {/* Info box urgenze */} -
-

+

+

Urgenze 24h

320 532.24.39 -

Disponibile 7 giorni su 7

+

Disponibile 7 giorni su 7

+
+ +
+
+
+ +
+
+

Richiesta non vincolante

+

+ L'invio del modulo non costituisce conferma automatica dell'appuntamento. + Il team verificherà disponibilità, tipologia di visita e urgenza del caso prima + di confermare data e orario. +

+
+
- {/* Orari */}
-

+

Orari di apertura

{[ - { days: "Lunedì — Venerdì", hours: "09:00 — 12:30 · 14:30 — 19:00" }, - { days: "Sabato", hours: "09:00 — 12:30" }, + { days: "Lunedi - Venerdi", hours: "09:00 - 12:30 · 14:30 - 19:00" }, + { days: "Sabato", hours: "09:00 - 12:30" }, { days: "Domenica", hours: "Solo urgenze" }, ].map((slot) => ( -
+
{slot.days} - {slot.hours} + {slot.hours}
))}
- {/* Colonna destra: form */} {submitted ? ( -
-
+
+

- Richiesta inviata! + Grazie {submittedName || "per la tua richiesta"}

-

- Abbiamo ricevuto la tua richiesta di appuntamento. Ti contatteremo entro 24 ore - per confermare data e orario. +

+ La tua richiesta di prenotazione è stata inviata correttamente.

+

+ Il team della Clinica Veterinaria Formiginese la prenderà in carico il prima + possibile e ti ricontatterà per confermare disponibilità, data e orario. +

+
+ Nota importante: la richiesta inviata + non equivale a una prenotazione già confermata. +
) : (
-

- Richiedi un appuntamento -

+
+

+ Richiedi un appuntamento +

+

+ Compila i campi richiesti e inviaci una proposta di data: sarà lo staff a + confermare la visita. +

+
- {/* Nome e telefono */} -
+
-
-
- {/* Animale */} -
+
-
-
- {/* Data e ora */} -
+
-
-
- {/* Note */}
-