Appointments, account stuff, adopt a pet changes

This commit is contained in:
augmentedpotato
2026-03-30 05:38:15 -06:00
parent 4dd57e3484
commit 00c5198c47
30 changed files with 2611 additions and 48 deletions

View File

@@ -0,0 +1,57 @@
"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";
import ProductProfile from "@/components/ProductProfile";
const API_BASE = "";
export default function ProductDetailPage() {
const { id } = useParams();
const [product, setProduct] = 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/products/${id}`)
.then((res) => {
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
return res.json();
})
.then((data) => setProduct(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, [id]);
return (
<main className="pet-detail-page">
<div className="pet-detail-container">
<Link href="/products" className="pet-detail-back"> Back to Products</Link>
{loading && <p className="adopt-status-msg">Loading product details...</p>}
{error && <p className="adopt-status-msg adopt-error">{error}</p>}
{!loading && !error && product && (
<ProductProfile
prodName={product.prodName}
categoryName={product.categoryName}
prodDesc={product.prodDesc}
prodPrice={product.prodPrice}
imageUrl={product.imageUrl}
/>
)}
</div>
</main>
);
}

133
web/app/products/page.js Normal file
View File

@@ -0,0 +1,133 @@
"use client";
import { useState, useEffect } from "react";
import ProductCard from "@/components/ProductCard";
const API_BASE = "";
export default function ProductsPage() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = 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(() => {
setLoading(true);
setError(null);
const params = new URLSearchParams({ page, size: PAGE_SIZE, sort: "prodId,asc" });
if (query) {
params.set("q", query);
}
fetch(`${API_BASE}/api/v1/products?${params}`)
.then((res) => {
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
return res.json();
})
.then((data) => {
setProducts(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="products-page">
<section className="products-hero">
<h1 className="products-hero-title">Shop Our Products</h1>
<p className="products-hero-subtitle">Everything your pet needs, all in one place</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 category..."
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>
</div>
</section>
<section className="adopt-grid-section">
{loading && <p className="adopt-status-msg">Loading products...</p>}
{error && (
<div className="adopt-error-box">
<p className="adopt-error-title">Failed to load products</p>
<code className="adopt-error-detail">{error}</code>
</div>
)}
{!loading && !error && products.length === 0 && (
<p className="adopt-status-msg">No products found.</p>
)}
{!loading && !error && products.length > 0 && (
<div className="adopt-grid">
{products.map((product) => (
<ProductCard
key={product.prodId}
prodId={product.prodId}
prodName={product.prodName}
categoryName={product.categoryName}
prodPrice={product.prodPrice}
imageUrl={product.imageUrl}
/>
))}
</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>
);
}