diff --git a/web/app/about/page.js b/web/app/about/page.js index 5ba61fe2..b15ad5e6 100644 --- a/web/app/about/page.js +++ b/web/app/about/page.js @@ -1,23 +1,23 @@ export default function AboutPage() { return ( -
-
-

About Leon's Pet Store

-

Pet care, adoption support, grooming, and everyday essentials in one place.

-
+
+
+

About Leon's Pet Store

+

Pet care, adoption support, grooming, and everyday essentials in one place.

+
-
-
-

What We Do

+
+
+

What We Do

Leon's Pet Store connects families with adoptable pets, helpful services, and quality products for day-to-day pet care.

-
-

Our Focus

-
    +
    +

    Our Focus

    +
    • Support responsible pet adoption
    • Provide grooming and care services
    • Offer reliable pet supplies and essentials
    • @@ -25,8 +25,8 @@ export default function AboutPage() {
    -
    -

    Visit the Store

    +
    +

    Visit the Store

    Browse adoptable pets, schedule appointments, shop products, or contact the team for help finding the right fit for a pet and household.

    diff --git a/web/app/adopt/[id]/page.js b/web/app/adopt/[id]/page.js index f2ade475..9a1ae6d3 100644 --- a/web/app/adopt/[id]/page.js +++ b/web/app/adopt/[id]/page.js @@ -15,7 +15,6 @@ export default function PetDetailPage() { useEffect(() => { if (!id) return; - fetch(`${API_BASE}/api/v1/pets/${id}`) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status} – ${res.statusText}`); @@ -27,12 +26,12 @@ export default function PetDetailPage() { }, [id]); return ( -
    -
    - ← Back to Pets +
    +
    + ← Back to Pets - {loading &&

    Loading pet details...

    } - {error &&

    {error}

    } + {loading &&

    Loading pet details...

    } + {error &&

    {error}

    } {!loading && !error && pet && ( + {children} + + ); +} + 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) => { @@ -42,23 +51,16 @@ export default function AdoptPage() { .catch(() => setSpeciesOptions([])); }, [selectedStoreId]); - useEffect(() => { - setSelectedBreed(""); - }, [selectedSpecies]); + useEffect(() => { setSelectedBreed(""); }, [selectedSpecies]); useEffect(() => { setLoading(true); setError(null); fetchAllPages((page) => { - const params = new URLSearchParams({ - page: String(page), - size: String(PAGE_SIZE), - sort: "id,asc", - }); + 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) @@ -67,171 +69,123 @@ export default function AdoptPage() { }, [query, selectedSpecies, selectedStoreId]); const breedOptions = useMemo( - () => - [...new Set(pets.map((p) => p.petBreed).filter(Boolean))].sort((a, b) => - a.localeCompare(b, undefined, { sensitivity: "base" }) - ), + () => [...new Set(pets.map((p) => p.petBreed).filter(Boolean))].sort((a, b) => + a.localeCompare(b, undefined, { sensitivity: "base" }) + ), [pets] ); - + const ITEMS_PER_PAGE = 24; const [currentPage, setCurrentPage] = useState(0); - const filteredPets = useMemo( () => (selectedBreed ? pets.filter((p) => p.petBreed === selectedBreed) : pets), [pets, selectedBreed] ); - const totalPages = Math.ceil(filteredPets.length / ITEMS_PER_PAGE); const displayedPets = filteredPets.slice(currentPage * ITEMS_PER_PAGE, (currentPage + 1) * ITEMS_PER_PAGE); - function handleSearch(e) { - e.preventDefault(); - setCurrentPage(0); - setQuery(search.trim()); - } - - function handleClearFilters() { - setSearch(""); - setQuery(""); - setSelectedSpecies(""); - setSelectedBreed(""); - setCurrentPage(0); - } - + function handleSearch(e) { e.preventDefault(); setCurrentPage(0); setQuery(search.trim()); } + function handleClearFilters() { setSearch(""); setQuery(""); setSelectedSpecies(""); setSelectedBreed(""); setCurrentPage(0); } const hasActiveFilters = query || selectedSpecies || selectedBreed; return ( -
    -
    -

    Find Your Perfect Companion

    -

    Give a loving pet their forever home

    -
    +
    +
    +

    Find Your Perfect Companion

    +

    Give a loving pet their forever home

    +
    -
    -
    -
    - +
    +
    +
    +
    -
    - +
    +
    -
    -
    +
    + setSearch(e.target.value)} /> - + - {hasActiveFilters && ( - )}
    -
    - {loading &&

    Loading pets...

    } - - {error && ( -

    Unable to load pets, please try again later.

    - )} - +
    + {loading &&

    Loading pets...

    } + {error &&

    Unable to load pets, please try again later.

    } {!loading && !error && displayedPets.length === 0 && ( -

    No pets found matching your filters.

    +

    No pets found matching your filters.

    )} {!loading && !error && displayedPets.length > 0 && ( -
    +
    {displayedPets.map((pet) => ( - + ))}
    )} + {!loading && !error && totalPages > 1 && ( -
    - + {(() => { const pages = []; const delta = 2; const left = Math.max(0, currentPage - delta); const right = Math.min(totalPages - 1, currentPage + delta); - if (left > 0) { - pages.push(0); - if (left > 1) pages.push("..."); - } + if (left > 0) { pages.push(0); if (left > 1) pages.push("..."); } for (let i = left; i <= right; i++) pages.push(i); - if (right < totalPages - 1) { - if (right < totalPages - 2) pages.push("..."); - pages.push(totalPages - 1); - } + if (right < totalPages - 1) { if (right < totalPages - 2) pages.push("..."); pages.push(totalPages - 1); } return pages.map((p, i) => p === "..." ? ( - + ) : ( - + ) ); })()} - +
    )}
    diff --git a/web/app/appointments/page.js b/web/app/appointments/page.js index 89bce924..5460dee3 100644 --- a/web/app/appointments/page.js +++ b/web/app/appointments/page.js @@ -39,8 +39,7 @@ function getAvailableServices(services, species) { } const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; -const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", -]; +const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; function DatePicker({ value, minDate, onChange }) { const today = new Date(); @@ -53,24 +52,13 @@ function DatePicker({ value, minDate, onChange }) { const [viewMonth, setViewMonth] = useState(parsed ? parsed.getMonth() : min.getMonth()); function prevMonth() { - if (viewMonth === 0) { - setViewMonth(11); - setViewYear((y) => y - 1); - } - - else { - setViewMonth((m) => m - 1); - } + if (viewMonth === 0) { setViewMonth(11); setViewYear((y) => y - 1); } + else { setViewMonth((m) => m - 1); } } function nextMonth() { - if (viewMonth === 11) { - setViewMonth(0); setViewYear((y) => y + 1); - } - - else { - setViewMonth((m) => m + 1); - } + if (viewMonth === 11) { setViewMonth(0); setViewYear((y) => y + 1); } + else { setViewMonth((m) => m + 1); } } const firstDay = new Date(viewYear, viewMonth, 1).getDay(); @@ -82,11 +70,8 @@ function DatePicker({ value, minDate, onChange }) { function selectDay(day) { const d = new Date(viewYear, viewMonth, day); - if (d < min) { - return; - } + if (d < min) return; const iso = `${viewYear}-${String(viewMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; - onChange(iso); } @@ -107,94 +92,16 @@ function DatePicker({ value, minDate, onChange }) { cells.push({ key: `day-${viewYear}-${viewMonth}-${String(d)}`, day: d }); } - const s = { - widget: { - border: "1px solid #ddd", - borderRadius: "10px", - overflow: "hidden", - background: "white", - userSelect: "none", - fontFamily: "inherit", - }, - header: { - display: "flex", - alignItems: "center", - justifyContent: "space-between", - background: "orange", - padding: "0.55rem 0.75rem", - }, - monthLabel: { - fontSize: "0.95rem", - fontWeight: 700, - color: "white", - }, - nav: { - background: "none", - border: "none", - color: "white", - fontSize: "1.5rem", - lineHeight: 1, - cursor: "pointer", - padding: "0 0.4rem", - borderRadius: "4px", - }, - grid: { - display: "grid", - gridTemplateColumns: "repeat(7, 1fr)", - gap: "3px", - padding: "0.6rem", - }, - dayName: { - textAlign: "center", - fontSize: "0.7rem", - fontWeight: 700, - color: "#aaa", - padding: "0.25rem 0", - textTransform: "uppercase", - }, - dayBase: { - display: "flex", - alignItems: "center", - justifyContent: "center", - aspectRatio: "1 / 1", - border: "none", - borderRadius: "6px", - background: "none", - fontSize: "0.875rem", - cursor: "pointer", - color: "#333", - fontFamily: "inherit", - padding: 0, - width: "100%", - }, - daySelected: { - background: "orange", - color: "white", - fontWeight: 700, - }, - dayDisabled: { - color: "#ccc", - cursor: "default", - }, - selectedLabel: { - textAlign: "center", - fontSize: "0.82rem", - color: "#666", - padding: "0.35rem 0.5rem 0.5rem", - borderTop: "1px solid #f0f0f0", - }, - }; - return ( -
    -
    - - {MONTHS[viewMonth]} {viewYear} - +
    +
    + + {MONTHS[viewMonth]} {viewYear} +
    -
    +
    {DAYS.map((d) => ( - {d} + {d} ))} {cells.map(({ key, day }) => day === null ? ( @@ -203,11 +110,8 @@ function DatePicker({ value, minDate, onChange }) {
    {parsed && ( -
    +
    Selected: {MONTHS[parsed.getMonth()]} {parsed.getDate()}, {parsed.getFullYear()}
    )} @@ -226,6 +130,13 @@ function DatePicker({ value, minDate, onChange }) { ); } +const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[#444]"; +const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]"; +const selectCls = `custom-select ${inputCls} bg-white cursor-pointer`; +const errorCls = "bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b] rounded-lg px-4 py-3 text-[0.9rem]"; +const successCls = "bg-[#f0fdf4] border border-[#bbf7d0] text-[#16a34a] rounded-lg px-4 py-3 text-[0.9rem]"; +const submitBtnCls = "py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"; + function AddPetModal({ token, onClose, onAdded }) { const [petName, setPetName] = useState(""); const [species, setSpecies] = useState(""); @@ -267,56 +178,38 @@ function AddPetModal({ token, onClose, onAdded }) { } return ( -
    -
    e.stopPropagation()}> -

    Add a New Pet

    - {petError &&
    {petError}
    } -
    -