From 2469c07fef415681827b2adcb69c267e4dd51d8d Mon Sep 17 00:00:00 2001 From: augmentedpotato Date: Tue, 7 Apr 2026 23:53:48 -0600 Subject: [PATCH] Can now add pets in the appointments page. --- web/app/appointments/page.js | 136 ++++++++++++++++++++++++++++++++--- web/app/globals.css | 38 ++++++++++ 2 files changed, 164 insertions(+), 10 deletions(-) diff --git a/web/app/appointments/page.js b/web/app/appointments/page.js index ce90e858..b4dd5cee 100644 --- a/web/app/appointments/page.js +++ b/web/app/appointments/page.js @@ -195,6 +195,100 @@ function DatePicker({ value, minDate, onChange }) { ); } +function AddPetModal({ token, onClose, onAdded }) { + const [petName, setPetName] = useState(""); + const [species, setSpecies] = useState(""); + const [breed, setBreed] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [petError, setPetError] = useState(null); + + async function handleSubmit(e) { + e.preventDefault(); + setPetError(null); + setSubmitting(true); + + try { + const res = await fetch(`${API_BASE}/api/v1/my-pets`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ petName, species, breed: breed || null }), + }); + + if (!res.ok) { + const data = await res.json().catch(() => null); + throw new Error(data?.message || `Request failed (${res.status})`); + } + + onAdded(); + onClose(); + } + + catch (err) { + setPetError(err.message); + } + + finally { + setSubmitting(false); + } + } + + return ( +
+
e.stopPropagation()}> +

Add a New Pet

+ {petError &&
{petError}
} +
+ + + +
+ + +
+
+
+
+ ); +} + function AppointmentsPage() { const { user, token, loading: authLoading } = useAuth(); const router = useRouter(); @@ -224,6 +318,8 @@ function AppointmentsPage() { const [appointments, setAppointments] = useState([]); const [loadingAppointments, setLoadingAppointments] = useState(false); + const [showAddPetModal, setShowAddPetModal] = useState(false); + const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; useEffect(() => { @@ -234,6 +330,16 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; }, [authLoading, user, router, preselectedPetId]); + const loadCustomerPets = useCallback(() => { + if (!token || !canBookAppointments) return; + fetch(`${API_BASE}/api/v1/my-pets`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((r) => r.json()) + .then((data) => setCustomerPets(Array.isArray(data) ? data : [])) + .catch(() => {}); + }, [token, canBookAppointments]); + useEffect(() => { if (!token) { return; @@ -256,15 +362,8 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; .then((data) => setAllPets(data.content ?? [])) .catch(() => {}); - if (canBookAppointments) { - fetch(`${API_BASE}/api/v1/my-pets`, { - headers: { Authorization: `Bearer ${token}` }, - }) - .then((r) => r.json()) - .then((data) => setCustomerPets(Array.isArray(data) ? data : [])) - .catch(() => {}); - } - }, [token, canBookAppointments]); + loadCustomerPets(); + }, [token, loadCustomerPets]); useEffect(() => { if (didPreselectRef.current) { @@ -482,10 +581,18 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; const petSectionLabel = isAdoptionService ? "Select a Pet to Adopt" : "Select Pet(s)"; const noPetsMessage = isAdoptionService ? "No pets are currently available for adoption." - : "No pets found. Please add your pets in your profile before booking."; + : "No pets found on your profile."; return (
+ {showAddPetModal && ( + setShowAddPetModal(false)} + onAdded={loadCustomerPets} + /> + )} +

Schedule an Appointment

Book a service for your pet or schedule a pet adoption visit

@@ -589,6 +696,15 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN"; {serviceId && (
{petSectionLabel} + {isCustomerPetService && ( + + )} {petsToShow.length === 0 ? (

{noPetsMessage}

) : isAdoptionService ? ( diff --git a/web/app/globals.css b/web/app/globals.css index 8571bfeb..43ec07e6 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -1351,6 +1351,44 @@ body { accent-color: orange; } +.appt-add-pet-btn { + display: inline-block; + margin-top: 0.5rem; + padding: 0.4rem 0.85rem; + background: none; + border: 1.5px solid orange; + border-radius: 6px; + color: orange; + font-size: 0.875rem; + font-weight: 600; + cursor: pointer; + transition: background 0.15s, color 0.15s; +} + +.appt-add-pet-btn:hover { + background: orange; + color: white; +} + +.appt-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.appt-modal { + background: white; + border-radius: 12px; + padding: 2rem; + width: 100%; + max-width: 420px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); +} + .appt-link { color: orange; font-weight: 600;