From 63162487b540aaa08b312171d912b1192f2c9639 Mon Sep 17 00:00:00 2001 From: augmentedpotato Date: Thu, 2 Apr 2026 09:32:42 -0600 Subject: [PATCH] Fix profile images --- web/app/appointments/page.js | 10 +---- web/app/profile/page.js | 75 +++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/web/app/appointments/page.js b/web/app/appointments/page.js index 676b953e..4643bf0e 100644 --- a/web/app/appointments/page.js +++ b/web/app/appointments/page.js @@ -615,15 +615,7 @@ function AppointmentsPage() { {submitting ? "Booking..." : isAdoptionService ? "Schedule Adoption Visit" : "Book Appointment"} - ) : ( -
-

Appointment Booking

-
-

Web appointment booking is currently available for customer accounts only.

-

Admin and staff accounts can still review appointment activity below.

-
-
- )} + ) : null}

{canBookAppointments ? "Your Appointments" : "Appointments"}

diff --git a/web/app/profile/page.js b/web/app/profile/page.js index d0b78c5e..ff79521e 100644 --- a/web/app/profile/page.js +++ b/web/app/profile/page.js @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; @@ -9,6 +9,7 @@ const API_BASE = ""; export default function ProfilePage() { const {user, token, loading, logout, refreshUser} = useAuth(); const router = useRouter(); + const petImageObjectUrlsRef = useRef([]); const [pets, setPets] = useState([]); const [loadingPets, setLoadingPets] = useState(false); @@ -25,6 +26,13 @@ export default function ProfilePage() { const [profileSuccess, setProfileSuccess] = useState(null); const [avatarSubmitting, setAvatarSubmitting] = useState(false); + const clearPetImageObjectUrls = useCallback(() => { + for (const objectUrl of petImageObjectUrlsRef.current) { + URL.revokeObjectURL(objectUrl); + } + petImageObjectUrlsRef.current = []; + }, []); + useEffect(() => { if (!loading && !user) { router.replace(`/login?next=${encodeURIComponent("/profile")}`); @@ -40,17 +48,64 @@ export default function ProfilePage() { }); }, [user]); - const loadPets = useCallback(() => { + const loadPets = useCallback(async () => { if (!token) return; setLoadingPets(true); - fetch(`${API_BASE}/api/v1/my-pets`, { - headers: { Authorization: `Bearer ${token}` }, - }) - .then((r) => r.json()) - .then(setPets) - .catch(() => {}) - .finally(() => setLoadingPets(false)); - }, [token]); + + try { + const response = await fetch(`${API_BASE}/api/v1/my-pets`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!response.ok) { + throw new Error(`Request failed (${response.status})`); + } + + const petData = await response.json(); + clearPetImageObjectUrls(); + + const petsWithResolvedImages = await Promise.all( + (Array.isArray(petData) ? petData : []).map(async (pet) => { + if (!pet.imageUrl) { + return pet; + } + + try { + const imageResponse = await fetch(`${API_BASE}${pet.imageUrl}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!imageResponse.ok) { + return { ...pet, imageUrl: null }; + } + + const blob = await imageResponse.blob(); + const objectUrl = URL.createObjectURL(blob); + petImageObjectUrlsRef.current.push(objectUrl); + + return { ...pet, imageUrl: objectUrl }; + } catch { + return { ...pet, imageUrl: null }; + } + }) + ); + + setPets(petsWithResolvedImages); + } + + catch { + } + + finally { + setLoadingPets(false); + } + }, [token, clearPetImageObjectUrls]); + + useEffect(() => { + return () => { + clearPetImageObjectUrls(); + }; + }, [clearPetImageObjectUrls]); useEffect(() => { if (user?.role === "CUSTOMER") {