150 lines
4.8 KiB
JavaScript
150 lines
4.8 KiB
JavaScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import PetCard from "@/components/PetCard";
|
||
|
||
const API_BASE = "";
|
||
|
||
export default function AdoptPage() {
|
||
const [pets, setPets] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState(null);
|
||
const [health, setHealth] = useState(null);
|
||
const [search, setSearch] = useState("");
|
||
const [query, setQuery] = useState("");
|
||
const [page, setPage] = useState(0);
|
||
const [totalPages, setTotalPages] = useState(0);
|
||
|
||
const PAGE_SIZE = 12;
|
||
|
||
useEffect(() => {
|
||
fetch(`${API_BASE}/api/v1/health`)
|
||
.then((res) => (res.ok ? setHealth("online") : setHealth("error")))
|
||
.catch(() => setHealth("offline"));
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
const params = new URLSearchParams({ page, size: PAGE_SIZE, sort: "id,asc" });
|
||
if (query) params.set("q", query);
|
||
|
||
fetch(`${API_BASE}/api/v1/pets?${params}`)
|
||
.then((res) => {
|
||
if (!res.ok) throw new Error(`HTTP ${res.status} – ${res.statusText}`);
|
||
return res.json();
|
||
})
|
||
.then((data) => {
|
||
setPets(data.content ?? []);
|
||
setTotalPages(data.totalPages ?? 0);
|
||
})
|
||
.catch((err) => setError(err.message))
|
||
.finally(() => setLoading(false));
|
||
}, [page, query]);
|
||
|
||
function handleSearch(e) {
|
||
e.preventDefault();
|
||
setPage(0);
|
||
setQuery(search.trim());
|
||
}
|
||
|
||
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-controls-row">
|
||
<form className="adopt-search-form" onSubmit={handleSearch}>
|
||
<input
|
||
className="adopt-search-input"
|
||
type="text"
|
||
placeholder="Search by name or species..."
|
||
value={search}
|
||
onChange={(e) => setSearch(e.target.value)}
|
||
/>
|
||
<button className="adopt-search-btn" type="submit">Search</button>
|
||
{query && (
|
||
<button
|
||
className="adopt-clear-btn"
|
||
type="button"
|
||
onClick={() => { setSearch(""); setQuery(""); setPage(0); }}
|
||
>
|
||
Clear
|
||
</button>
|
||
)}
|
||
</form>
|
||
<span
|
||
className={`backend-status backend-status--${health ?? "checking"}`}
|
||
title={
|
||
health === "online" ? "Backend online" :
|
||
health === "offline" ? "Backend offline" :
|
||
health === "error" ? "Backend error" : "Checking…"
|
||
}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="adopt-grid-section">
|
||
{loading && <p className="adopt-status-msg">Loading pets...</p>}
|
||
|
||
{error && (
|
||
<div className="adopt-error-box">
|
||
<p className="adopt-error-title">Failed to load pets</p>
|
||
<code className="adopt-error-detail">{error}</code>
|
||
<p className="adopt-error-hint">
|
||
{health === "offline"
|
||
? "The Spring Boot backend is not reachable. Make sure it is running in IntelliJ on port 8080."
|
||
: health === "error"
|
||
? "The backend responded with an error. Check the IntelliJ Run console for stack traces."
|
||
: "The backend is reachable but the /pets endpoint failed. Check the IntelliJ Run console."}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{!loading && !error && pets.length === 0 && (
|
||
<p className="adopt-status-msg">No pets found.</p>
|
||
)}
|
||
|
||
{!loading && !error && pets.length > 0 && (
|
||
<div className="adopt-grid">
|
||
{pets.map((pet) => (
|
||
<PetCard
|
||
key={pet.petId}
|
||
petId={pet.petId}
|
||
petName={pet.petName}
|
||
petSpecies={pet.petSpecies}
|
||
petStatus={pet.petStatus}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{!loading && totalPages > 1 && (
|
||
<div className="adopt-pagination">
|
||
<button
|
||
className="pagination-btn"
|
||
disabled={page === 0}
|
||
onClick={() => setPage((p) => p - 1)}
|
||
>
|
||
← Prev
|
||
</button>
|
||
<span className="pagination-info">Page {page + 1} of {totalPages}</span>
|
||
<button
|
||
className="pagination-btn"
|
||
disabled={page >= totalPages - 1}
|
||
onClick={() => setPage((p) => p + 1)}
|
||
>
|
||
Next →
|
||
</button>
|
||
</div>
|
||
)}
|
||
</section>
|
||
</main>
|
||
);
|
||
}
|