fix(portal/book): wire Rebook Now button + date format validation (GRO-265, GRO-266)
* fix(portal): wire Rebook Now button to navigate to booking wizard (GRO-265) The "Rebook Now" button on the Report Card detail view had no click handler. Now navigates to /admin/book with pet info pre-filled via URL params (petName, serviceName). Button text changed from "Book Now" to "Rebook Now" per the bug report. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(book): pre-fill form from URL params to ensure React state is set Add useSearchParams to read URL parameters (e.g., ?clientName=Jane) and sync them to the BookingBody state on mount via useEffect. This ensures validation checks React state, not empty initial state. Fixes GRO-255 Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(book): add inline validation for date input format (GRO-266) Date picker now shows a clear error when the value doesn't match YYYY-MM-DD, instead of silently failing with a browser console warning. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(portal): wire Rebook Now button + clean .js artifacts (GRO-265) Cherry-picked from contaminated PR #160: - ReportCards.tsx: Rebook Now button navigates to /admin/book with pet info - Book.tsx: pre-fill form from URL params (GRO-255) - Book.tsx: inline date validation (GRO-266) Also removes compiled .js artifacts (Book.js, ReportCards.js) that were incorrectly committed. Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: groombook-ci[bot] <ci@groombook.bot> Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit was merged in pull request #162.
This commit is contained in:
committed by
GitHub
parent
20920022a6
commit
4dabb25ee1
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
import type { Service } from "@groombook/types";
|
import type { Service } from "@groombook/types";
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
@@ -107,6 +108,7 @@ export function BookPage() {
|
|||||||
|
|
||||||
// Step 2 — date & time
|
// Step 2 — date & time
|
||||||
const [date, setDate] = useState(todayIso());
|
const [date, setDate] = useState(todayIso());
|
||||||
|
const [dateError, setDateError] = useState<string | null>(null);
|
||||||
const [slots, setSlots] = useState<string[]>([]);
|
const [slots, setSlots] = useState<string[]>([]);
|
||||||
const [slotsLoading, setSlotsLoading] = useState(false);
|
const [slotsLoading, setSlotsLoading] = useState(false);
|
||||||
const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
|
const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
|
||||||
@@ -125,6 +127,28 @@ export function BookPage() {
|
|||||||
});
|
});
|
||||||
const [formError, setFormError] = useState<string | null>(null);
|
const [formError, setFormError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Pre-fill form from URL params (e.g., ?clientName=Jane&clientEmail=jane@example.com)
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
useEffect(() => {
|
||||||
|
const clientName = searchParams.get("clientName");
|
||||||
|
const clientEmail = searchParams.get("clientEmail");
|
||||||
|
const clientPhone = searchParams.get("clientPhone");
|
||||||
|
const petName = searchParams.get("petName");
|
||||||
|
const petSpecies = searchParams.get("petSpecies");
|
||||||
|
const petBreed = searchParams.get("petBreed");
|
||||||
|
if (clientName || clientEmail || clientPhone || petName || petSpecies || petBreed) {
|
||||||
|
setForm((f) => ({
|
||||||
|
...f,
|
||||||
|
...(clientName && { clientName }),
|
||||||
|
...(clientEmail && { clientEmail }),
|
||||||
|
...(clientPhone && { clientPhone }),
|
||||||
|
...(petName && { petName }),
|
||||||
|
...(petSpecies && { petSpecies }),
|
||||||
|
...(petBreed && { petBreed }),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
// Step 4 — result
|
// Step 4 — result
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [result, setResult] = useState<BookingResult | null>(null);
|
const [result, setResult] = useState<BookingResult | null>(null);
|
||||||
@@ -328,8 +352,21 @@ export function BookPage() {
|
|||||||
value={date}
|
value={date}
|
||||||
min={todayIso()}
|
min={todayIso()}
|
||||||
style={{ ...input, width: "auto" }}
|
style={{ ...input, width: "auto" }}
|
||||||
onChange={(e) => setDate(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
// HTML5 date input enforces yyyy-MM-dd; empty value means invalid format
|
||||||
|
if (!val) {
|
||||||
|
setDateError("Please enter a valid date (YYYY-MM-DD).");
|
||||||
|
setDate("");
|
||||||
|
} else {
|
||||||
|
setDateError(null);
|
||||||
|
setDate(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{dateError && (
|
||||||
|
<p style={{ color: "#dc2626", fontSize: 12, marginTop: 4 }}>{dateError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: "1.25rem" }}>
|
<div style={{ marginBottom: "1.25rem" }}>
|
||||||
|
|||||||
@@ -241,8 +241,17 @@ function ReportCardDetail({ card, onBack }: { card: Appointment; onBack: () => v
|
|||||||
<p className="text-sm font-medium text-stone-800">Book your next visit</p>
|
<p className="text-sm font-medium text-stone-800">Book your next visit</p>
|
||||||
<p className="text-xs text-stone-500">Schedule your next grooming appointment</p>
|
<p className="text-xs text-stone-500">Schedule your next grooming appointment</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="px-4 py-2 bg-(--color-accent) text-white rounded-lg text-sm font-medium hover:bg-(--color-accent-hover)">
|
<button
|
||||||
Book Now
|
onClick={() => {
|
||||||
|
// TODO: Pre-select the service from report card (serviceId/serviceName) once BookPage supports service pre-selection via URL param
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (card.petName) params.set("petName", card.petName);
|
||||||
|
if (card.serviceName) params.set("serviceName", card.serviceName);
|
||||||
|
window.location.href = `/admin/book${params.size > 0 ? `?${params.toString()}` : ""}`;
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-(--color-accent) text-white rounded-lg text-sm font-medium hover:bg-(--color-accent-hover)"
|
||||||
|
>
|
||||||
|
Rebook Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user