187 lines
6.0 KiB
JavaScript
187 lines
6.0 KiB
JavaScript
"use client";
|
|
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import PetCard from "@/components/PetCard";
|
|
import { fetchAllPages } from "@/lib/fetchAllPages";
|
|
import { useCart } from "@/context/CartContext";
|
|
|
|
const API_BASE = "";
|
|
const PAGE_SIZE = 10000;
|
|
|
|
export default function AdoptPage() {
|
|
const { selectedStoreId } = useCart();
|
|
|
|
// pets = everything returned by the server (filtered by species + store + text query)
|
|
const [pets, setPets] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState(null);
|
|
|
|
const [search, setSearch] = useState("");
|
|
const [query, setQuery] = useState("");
|
|
|
|
const [selectedSpecies, setSelectedSpecies] = useState("");
|
|
const [selectedBreed, setSelectedBreed] = useState("");
|
|
|
|
// Species options come from a dedicated fetch (only store-filtered, no species filter)
|
|
const [speciesOptions, setSpeciesOptions] = useState([]);
|
|
|
|
useEffect(() => {
|
|
setSelectedSpecies("");
|
|
const params = new URLSearchParams({ page: "0", size: String(PAGE_SIZE) });
|
|
if (selectedStoreId) params.set("storeId", String(selectedStoreId));
|
|
|
|
fetch(`${API_BASE}/api/v1/pets?${params}`)
|
|
.then((r) => (r.ok ? r.json() : null))
|
|
.then((data) => {
|
|
const items = data?.content ?? [];
|
|
const species = [...new Set(items.map((p) => p.petSpecies).filter(Boolean))].sort((a, b) =>
|
|
a.localeCompare(b, undefined, { sensitivity: "base" })
|
|
);
|
|
setSpeciesOptions(species);
|
|
})
|
|
.catch(() => setSpeciesOptions([]));
|
|
}, [selectedStoreId]);
|
|
|
|
useEffect(() => {
|
|
setSelectedBreed("");
|
|
}, [selectedSpecies]);
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
setError(null);
|
|
fetchAllPages((page) => {
|
|
const params = new URLSearchParams({
|
|
page: String(page),
|
|
size: String(PAGE_SIZE),
|
|
sort: "id,asc",
|
|
});
|
|
if (query) params.set("q", query);
|
|
if (selectedSpecies) params.set("species", selectedSpecies);
|
|
if (selectedStoreId) params.set("storeId", String(selectedStoreId));
|
|
|
|
return `${API_BASE}/api/v1/pets?${params}`;
|
|
})
|
|
.then(setPets)
|
|
.catch((err) => setError(err.message))
|
|
.finally(() => setLoading(false));
|
|
}, [query, selectedSpecies, selectedStoreId]);
|
|
|
|
const breedOptions = useMemo(
|
|
() =>
|
|
[...new Set(pets.map((p) => p.petBreed).filter(Boolean))].sort((a, b) =>
|
|
a.localeCompare(b, undefined, { sensitivity: "base" })
|
|
),
|
|
[pets]
|
|
);
|
|
|
|
const displayedPets = useMemo(
|
|
() => (selectedBreed ? pets.filter((p) => p.petBreed === selectedBreed) : pets),
|
|
[pets, selectedBreed]
|
|
);
|
|
|
|
function handleSearch(e) {
|
|
e.preventDefault();
|
|
setQuery(search.trim());
|
|
}
|
|
|
|
function handleClearFilters() {
|
|
setSearch("");
|
|
setQuery("");
|
|
setSelectedSpecies("");
|
|
setSelectedBreed("");
|
|
}
|
|
|
|
const hasActiveFilters = query || selectedSpecies || selectedBreed;
|
|
|
|
return (
|
|
<main className="adopt-page">
|
|
<section className="adopt-hero">
|
|
<h1 className="adopt-hero-title">Find Your Perfect Companion</h1>
|
|
<p className="adopt-hero-subtitle">Give a loving pet their forever home</p>
|
|
<div className="title-decoration"></div>
|
|
</section>
|
|
|
|
<section className="adopt-controls">
|
|
<div className="adopt-filters-row">
|
|
<div className="adopt-filter-group">
|
|
<label className="adopt-filter-label" htmlFor="species-filter">Species</label>
|
|
<select
|
|
id="species-filter"
|
|
className="adopt-filter-select"
|
|
value={selectedSpecies}
|
|
onChange={(e) => setSelectedSpecies(e.target.value)}
|
|
>
|
|
<option value="">All Species</option>
|
|
{speciesOptions.map((s) => (
|
|
<option key={s} value={s}>{s}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="adopt-filter-group">
|
|
<label className="adopt-filter-label" htmlFor="breed-filter">Breed</label>
|
|
<select
|
|
id="breed-filter"
|
|
className="adopt-filter-select"
|
|
value={selectedBreed}
|
|
onChange={(e) => setSelectedBreed(e.target.value)}
|
|
disabled={!selectedSpecies}
|
|
>
|
|
<option value="">{!selectedSpecies ? "Select a species first" : "All Breeds"}</option>
|
|
{breedOptions.map((b) => (
|
|
<option key={b} value={b}>{b}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="adopt-controls-row">
|
|
<form className="adopt-search-form" onSubmit={handleSearch}>
|
|
<input
|
|
className="adopt-search-input"
|
|
type="text"
|
|
placeholder="Search by name, species, or breed..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
<button className="adopt-search-btn" type="submit">Search</button>
|
|
</form>
|
|
|
|
{hasActiveFilters && (
|
|
<button className="adopt-clear-btn" type="button" onClick={handleClearFilters}>
|
|
Clear Filters
|
|
</button>
|
|
)}
|
|
</div>
|
|
</section>
|
|
|
|
<section className="adopt-grid-section">
|
|
{loading && <p className="adopt-status-msg">Loading pets...</p>}
|
|
|
|
{error && (
|
|
<p className="adopt-status-msg">Unable to load pets, please try again later.</p>
|
|
)}
|
|
|
|
{!loading && !error && displayedPets.length === 0 && (
|
|
<p className="adopt-status-msg">No pets found matching your filters.</p>
|
|
)}
|
|
|
|
{!loading && !error && displayedPets.length > 0 && (
|
|
<div className="adopt-grid">
|
|
{displayedPets.map((pet) => (
|
|
<PetCard
|
|
key={pet.petId}
|
|
petId={pet.petId}
|
|
petName={pet.petName}
|
|
petSpecies={pet.petSpecies}
|
|
petStatus={pet.petStatus}
|
|
imageUrl={pet.imageUrl}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|