// Modern Vet — shared components
const { useState, useEffect, useRef, useMemo, useCallback } = React;
// ── ICONS ─────────────────────────────────────────────────────────
const Icon = ({ name, className = "" }) => {
const common = { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round", className };
switch (name) {
case "phone": return ;
case "whatsapp": return ;
case "menu": return ;
case "close": return ;
case "arrow-right": return ;
case "chevron-down": return ;
case "chevron-right": return ;
case "map-pin": return ;
case "clock": return ;
case "emergency": return ;
case "consult": return ;
case "syringe": return ;
case "surgery": return ;
case "dentistry": return ;
case "heart": return ;
case "brain": return ;
case "oncology": return ;
case "eye": return ;
case "ortho": return ;
case "imaging": return ;
case "physio": return ;
case "grooming": return ;
case "mobile": return ;
case "chip": return ;
case "paw": return ;
case "check": return ;
case "search": return ;
case "globe": return ;
case "calendar": return ;
default: return null;
}
};
// ── BRAND MARK ────────────────────────────────────────────────────
// The wedge / pet silhouette mark from the logo, simplified as a recurring graphic device
const Wedge = ({ className = "", color = "currentColor" }) => (
);
// ── LOGO LOCKUP ───────────────────────────────────────────────────
const Logo = ({ onNavy = false, onClick }) => (
{ e.preventDefault(); onClick && onClick(); }} className="logo">
MODERN VET
SINCE 1995 · DUBAI
);
// ── NAV ───────────────────────────────────────────────────────────
const NAV_ITEMS = [
{ label: "Services", href: "#/services" },
{ label: "Vets & Team", href: "#/vets" },
{ label: "Branches", href: "#/branches" },
{ label: "Prices", href: "#/prices" },
{ label: "About", href: "#/about" },
];
const Nav = ({ onBook, currentHash, branch, onBranchChange }) => {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [branchOpen, setBranchOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const activeBranch = BRANCHES.find(b => b.slug === branch) || BRANCHES[0];
return (
(window.location.hash = "/")} />
setBranchOpen(false)}>
{branchOpen && (
Choose your nearest branch
{BRANCHES.map(b => (
))}
)}
{open && (
)}
);
};
// ── EMERGENCY STRIP ───────────────────────────────────────────────
const EmergencyStrip = () => (
);
// ── FOOTER ────────────────────────────────────────────────────────
const Footer = () => (
);
// ── MOBILE BOTTOM BAR ─────────────────────────────────────────────
const MobileBar = ({ onBook }) => (
);
// ── BOOKING MODAL (multi-step) ────────────────────────────────────
const BookModal = ({ open, onClose, branch, defaultService }) => {
const [step, setStep] = useState(0);
const [data, setData] = useState({
branch: branch || "al-wasl",
service: defaultService || "pet-consultation",
petType: "Dog",
petName: "",
petAge: "",
date: "",
time: "10:00",
name: "",
phone: "",
email: "",
notes: "",
});
useEffect(() => { if (open) { setStep(0); setData(d => ({ ...d, branch: branch || d.branch, service: defaultService || d.service })); } }, [open, branch, defaultService]);
useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
if (open) window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
if (!open) return null;
const update = (k, v) => setData(d => ({ ...d, [k]: v }));
const activeBranch = BRANCHES.find(b => b.slug === data.branch);
const activeService = SERVICES.find(s => s.slug === data.service);
const steps = ["Branch & service", "Your pet", "Date & contact", "Confirm"];
const next = () => setStep(s => Math.min(s + 1, steps.length - 1));
const prev = () => setStep(s => Math.max(s - 1, 0));
// Date helpers — generate next 14 days
const days = Array.from({ length: 14 }, (_, i) => {
const d = new Date(); d.setDate(d.getDate() + i);
return d;
});
const fmtDay = (d) => d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
const TIMES = ["08:00", "09:00", "10:00", "11:00", "12:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00"];
return (
e.stopPropagation()} role="dialog" aria-label="Book appointment">
Book an appointment
A vet you can call by name.
No phone tag. Pick a branch, a time and we'll confirm by WhatsApp within an hour.
{steps.map((s, i) => (
-
{i < step ? "✓" : i + 1}
{s}
))}
{step === 0 && (
Where would you like to be seen?
{BRANCHES.map(b => (
))}
What is this visit for?
{SERVICES.filter(s => !s.isEmergency).slice(0, 9).map(s => (
))}
)}
{step === 1 && (
)}
{step === 2 && (
)}
{step === 3 && (
All set — please confirm
- Branch
- {activeBranch?.name}
- Service
- {activeService?.name}
- Pet
- {data.petName || "—"} · {data.petType}{data.petAge ? ` · ${data.petAge}` : ""}
- When
- {data.date || "—"} at {data.time}
- You
- {data.name || "—"} · {data.phone || "—"}
We'll send a confirmation on WhatsApp within an hour. If your pet needs care sooner, please call 800-VET.
A vet will see {data.petName || "your pet"} at {activeBranch?.name.replace(" Hospital", "").replace(" Clinic", "")}.
)}
Step {step + 1} of {steps.length}
{step > 0 && }
{step < steps.length - 1 && }
{step === steps.length - 1 && }
);
};
// ── PHOTO SLOT (placeholder for tasteful image use) ───────────────
const Photo = ({ src, alt, ratio = "4 / 3", className = "", focal = "center" }) => (
);
// ── SECTION HEADER ────────────────────────────────────────────────
const SectionHead = ({ eyebrow, title, sub, align = "left", onNavy = false }) => (
{eyebrow && {eyebrow}
}
{title}
{sub && {sub}
}
);
Object.assign(window, { Icon, Wedge, Logo, Nav, EmergencyStrip, Footer, MobileBar, BookModal, Photo, SectionHead });