feat(GRO-1174): add pet size/coat dropdowns to booking wizard
- Add Pet Size dropdown (Small, Medium, Large, X-Large) after breed field - Add Coat Type dropdown (Smooth, Double, Curly, Wire, Long, Hairless) - Pass petSizeCategory + petCoatType as query params to availability endpoint - Include petSizeCategory + petCoatType in POST /appointments body - Show "appointment" duration label on confirm (service duration only) - Display pet size/coat on confirmation card when provided - Pre-fill from URL params - Reset form resets all new fields Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
+57
-7
@@ -13,6 +13,8 @@ interface BookingBody {
|
||||
petName: string;
|
||||
petSpecies: string;
|
||||
petBreed: string;
|
||||
petSizeCategory: string;
|
||||
petCoatType: string;
|
||||
notes: string;
|
||||
}
|
||||
|
||||
@@ -123,6 +125,8 @@ export function BookPage() {
|
||||
petName: "",
|
||||
petSpecies: "",
|
||||
petBreed: "",
|
||||
petSizeCategory: "",
|
||||
petCoatType: "",
|
||||
notes: "",
|
||||
});
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
@@ -136,7 +140,9 @@ export function BookPage() {
|
||||
const petName = searchParams.get("petName");
|
||||
const petSpecies = searchParams.get("petSpecies");
|
||||
const petBreed = searchParams.get("petBreed");
|
||||
if (clientName || clientEmail || clientPhone || petName || petSpecies || petBreed) {
|
||||
const petSizeCategory = searchParams.get("petSizeCategory");
|
||||
const petCoatType = searchParams.get("petCoatType");
|
||||
if (clientName || clientEmail || clientPhone || petName || petSpecies || petBreed || petSizeCategory || petCoatType) {
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
...(clientName && { clientName }),
|
||||
@@ -145,6 +151,8 @@ export function BookPage() {
|
||||
...(petName && { petName }),
|
||||
...(petSpecies && { petSpecies }),
|
||||
...(petBreed && { petBreed }),
|
||||
...(petSizeCategory && { petSizeCategory }),
|
||||
...(petCoatType && { petCoatType }),
|
||||
}));
|
||||
}
|
||||
}, [searchParams]);
|
||||
@@ -168,14 +176,18 @@ export function BookPage() {
|
||||
if (!selectedService || !date) return;
|
||||
setSlotsLoading(true);
|
||||
setSelectedSlot(null);
|
||||
fetch(
|
||||
`/api/book/availability?serviceId=${encodeURIComponent(selectedService.id)}&date=${encodeURIComponent(date)}`
|
||||
)
|
||||
const params = new URLSearchParams({
|
||||
serviceId: selectedService.id,
|
||||
date,
|
||||
});
|
||||
if (form.petSizeCategory) params.set("petSizeCategory", form.petSizeCategory);
|
||||
if (form.petCoatType) params.set("petCoatType", form.petCoatType);
|
||||
fetch(`/api/book/availability?${params.toString()}`)
|
||||
.then((r) => r.json() as Promise<string[]>)
|
||||
.then(setSlots)
|
||||
.catch(() => setSlots([]))
|
||||
.finally(() => setSlotsLoading(false));
|
||||
}, [selectedService, date]);
|
||||
}, [selectedService, date, form.petSizeCategory, form.petCoatType]);
|
||||
|
||||
function goToStep2(svc: Service) {
|
||||
setSelectedService(svc);
|
||||
@@ -214,6 +226,8 @@ export function BookPage() {
|
||||
petName: form.petName,
|
||||
petSpecies: form.petSpecies,
|
||||
petBreed: form.petBreed || undefined,
|
||||
petSizeCategory: form.petSizeCategory || undefined,
|
||||
petCoatType: form.petCoatType || undefined,
|
||||
notes: form.notes || undefined,
|
||||
}),
|
||||
});
|
||||
@@ -494,6 +508,36 @@ export function BookPage() {
|
||||
placeholder="Golden Retriever"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={label}>Pet size</label>
|
||||
<select
|
||||
style={input}
|
||||
value={form.petSizeCategory}
|
||||
onChange={(e) => setForm((f) => ({ ...f, petSizeCategory: e.target.value }))}
|
||||
>
|
||||
<option value="">Select size…</option>
|
||||
<option value="small">Small (under 15 lbs)</option>
|
||||
<option value="medium">Medium (15–40 lbs)</option>
|
||||
<option value="large">Large (40–80 lbs)</option>
|
||||
<option value="x-large">X-Large (over 80 lbs)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={label}>Coat type</label>
|
||||
<select
|
||||
style={input}
|
||||
value={form.petCoatType}
|
||||
onChange={(e) => setForm((f) => ({ ...f, petCoatType: e.target.value }))}
|
||||
>
|
||||
<option value="">Select coat…</option>
|
||||
<option value="smooth">Smooth</option>
|
||||
<option value="double">Double</option>
|
||||
<option value="curly">Curly</option>
|
||||
<option value="wire">Wire</option>
|
||||
<option value="long">Long</option>
|
||||
<option value="hairless">Hairless</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={label}>Notes for groomer</label>
|
||||
<textarea
|
||||
@@ -528,7 +572,7 @@ export function BookPage() {
|
||||
<div>
|
||||
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Service</div>
|
||||
<div style={{ fontWeight: 600 }}>{selectedService.name}</div>
|
||||
<div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes)}</div>
|
||||
<div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes)} appointment</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Date & Time</div>
|
||||
@@ -545,6 +589,11 @@ export function BookPage() {
|
||||
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Pet</div>
|
||||
<div style={{ fontWeight: 600 }}>{form.petName}</div>
|
||||
<div style={{ color: "#6b7280", textTransform: "capitalize" }}>{form.petSpecies}{form.petBreed ? ` · ${form.petBreed}` : ""}</div>
|
||||
{(form.petSizeCategory || form.petCoatType) && (
|
||||
<div style={{ color: "#6b7280", fontSize: 12, marginTop: 2 }}>
|
||||
{form.petSizeCategory ? `${form.petSizeCategory} · ` : ""}{form.petCoatType ? form.petCoatType : ""}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{form.notes && (
|
||||
<div style={{ gridColumn: "1 / -1" }}>
|
||||
@@ -599,7 +648,8 @@ export function BookPage() {
|
||||
setResult(null);
|
||||
setForm({
|
||||
serviceId: "", startTime: "", clientName: "", clientEmail: "",
|
||||
clientPhone: "", petName: "", petSpecies: "", petBreed: "", notes: "",
|
||||
clientPhone: "", petName: "", petSpecies: "", petBreed: "",
|
||||
petSizeCategory: "", petCoatType: "", notes: "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user