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") {