Adopt page, minor adjustment to backend
This commit is contained in:
52
web/app/adopt/[id]/page.js
Normal file
52
web/app/adopt/[id]/page.js
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import PetProfile from "@/components/PetProfile";
|
||||
|
||||
const API_BASE = "";
|
||||
|
||||
export default function PetDetailPage() {
|
||||
const { id } = useParams();
|
||||
const [pet, setPet] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetch(`${API_BASE}/api/v1/pets/${id}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status} – ${res.statusText}`);
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => setPet(data))
|
||||
.catch((err) => setError(err.message))
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<main className="pet-detail-page">
|
||||
<div className="pet-detail-container">
|
||||
<Link href="/adopt" className="pet-detail-back">← Back to Pets</Link>
|
||||
|
||||
{loading && <p className="adopt-status-msg">Loading pet details...</p>}
|
||||
{error && <p className="adopt-status-msg adopt-error">{error}</p>}
|
||||
|
||||
{!loading && !error && pet && (
|
||||
<PetProfile
|
||||
petName={pet.petName}
|
||||
petSpecies={pet.petSpecies}
|
||||
petBreed={pet.petBreed}
|
||||
petAge={pet.petAge}
|
||||
petStatus={pet.petStatus}
|
||||
petPrice={pet.petPrice}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
149
web/app/adopt/page.js
Normal file
149
web/app/adopt/page.js
Normal file
@@ -0,0 +1,149 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user