Styling refactor

This commit is contained in:
augmentedpotato
2026-04-18 16:12:43 -06:00
committed by Harkamal Randhawa
parent 148b587c05
commit 79c42574f6
21 changed files with 829 additions and 4509 deletions

View File

@@ -1,54 +1,50 @@
import Link from "next/link";
import Image from "next/image";
const linkCls = "text-[#2f2f2f] no-underline text-[0.95rem] opacity-85 transition-opacity hover:opacity-100 hover:underline";
export default function Footer() {
return (
<footer className="site-footer">
<div className="footer-container">
<footer className="bg-[#e68672] text-[#2f2f2f] mt-16 rounded-t-[10px]">
<div className="max-w-[1200px] mx-auto px-8 pt-12 pb-8 grid [grid-template-columns:2fr_1fr_1fr_1fr] gap-8 max-[900px]:[grid-template-columns:1fr_1fr] max-[550px]:grid-cols-1">
<div className="footer-brand">
<Image
src="/logo_simple.png"
alt="Leon's Pet Store logo"
width={50}
height={50}
className="footer-logo"
/>
<p className="footer-tagline">
<div className="max-[900px]:[grid-column:1/-1]">
<Image src="/logo_simple.png" alt="Leon's Pet Store logo" width={50} height={50} className="rounded-full mb-3" />
<p className="text-[0.95rem] leading-relaxed opacity-90 max-w-[260px]">
Your neighbourhood pet store!
</p>
</div>
<div className="footer-section">
<h3 className="footer-heading">Quick Links</h3>
<ul className="footer-links">
<li><Link href="/">Home</Link></li>
<li><Link href="/adopt">Adopt a Pet</Link></li>
<li><Link href="/products">Online Store</Link></li>
<li><Link href="/appointments">Schedule an Appointment</Link></li>
<li><Link href="/ai-chat">AI Assistant</Link></li>
<div>
<h3 className="text-base font-bold mb-4 uppercase tracking-[0.05em]">Quick Links</h3>
<ul className="list-none p-0 m-0 flex flex-col gap-2">
<li><Link href="/" className={linkCls}>Home</Link></li>
<li><Link href="/adopt" className={linkCls}>Adopt a Pet</Link></li>
<li><Link href="/products" className={linkCls}>Online Store</Link></li>
<li><Link href="/appointments" className={linkCls}>Schedule an Appointment</Link></li>
<li><Link href="/ai-chat" className={linkCls}>AI Assistant</Link></li>
</ul>
</div>
<div className="footer-section">
<h3 className="footer-heading">Company</h3>
<ul className="footer-links">
<li><Link href="/about">About Us</Link></li>
<li><Link href="/contact">Contact Us</Link></li>
<div>
<h3 className="text-base font-bold mb-4 uppercase tracking-[0.05em]">Company</h3>
<ul className="list-none p-0 m-0 flex flex-col gap-2">
<li><Link href="/about" className={linkCls}>About Us</Link></li>
<li><Link href="/contact" className={linkCls}>Contact Us</Link></li>
</ul>
</div>
<div className="footer-section">
<h3 className="footer-heading">Contact</h3>
<ul className="footer-links footer-contact">
<li>(403) 123-4567</li>
<li>support@leonspetstore.com</li>
<div>
<h3 className="text-base font-bold mb-4 uppercase tracking-[0.05em]">Contact</h3>
<ul className="list-none p-0 m-0 flex flex-col gap-2">
<li className="text-[0.95rem] opacity-85">(403) 123-4567</li>
<li className="text-[0.95rem] opacity-85">support@leonspetstore.com</li>
</ul>
</div>
</div>
<div className="footer-bottom">
<div className="border-t border-[rgba(47,47,47,0.2)] text-center px-8 py-4 text-[0.85rem] opacity-80">
<p>&copy; {new Date().getFullYear()} Leon&apos;s Pet Store. All rights reserved.</p>
</div>
</footer>

View File

@@ -7,6 +7,22 @@ import { useEffect, useState } from "react";
import { useAuth } from "@/context/AuthContext";
import { useCart } from "@/context/CartContext";
const drawerLinkCls = "block text-[#2f2f2f] no-underline text-[1.05rem] font-medium px-2 py-[0.65rem] rounded-md transition-colors hover:bg-[rgba(47,47,47,0.1)]";
const navLinkCls = "text-[#2f2f2f] no-underline text-[1.05rem] font-semibold px-4 py-2 rounded-md transition-all duration-[250ms] hover:bg-white/25";
const cartBtnCls = "relative inline-flex items-center text-[1.4rem] no-underline mr-2 px-[0.4rem] py-[0.2rem] rounded-md transition-colors hover:bg-white/20";
const cartBadgeCls = "absolute -top-1 -right-1.5 bg-[#e53935] text-white rounded-full text-[0.65rem] font-bold min-w-[18px] h-[18px] flex items-center justify-center px-[3px] leading-none";
function CartIcon({ itemCount, onClick }) {
return (
<Link href="/cart" className={cartBtnCls} aria-label="Cart" onClick={onClick}>
🛒
{itemCount > 0 && (
<span className={cartBadgeCls}>{itemCount > 99 ? "99+" : itemCount}</span>
)}
</Link>
);
}
export default function DisplayNav() {
const { user, logout, loading } = useAuth();
const { itemCount, selectedStoreId, setStoreId } = useCart();
@@ -27,140 +43,98 @@ export default function DisplayNav() {
setMenuOpen(false);
}
function closeMenu() {
setMenuOpen(false);
}
function closeMenu() { setMenuOpen(false); }
const storeSelect = (extraCls = "") => user && stores.length > 0 && (
<select
className={`bg-[rgba(47,47,47,0.1)] text-[#2f2f2f] border border-[rgba(47,47,47,0.35)] rounded-md px-[0.6rem] py-[0.3rem] text-[0.9rem] cursor-pointer outline-none transition-colors hover:bg-[rgba(47,47,47,0.2)] ${extraCls}`}
value={selectedStoreId ?? ""}
onChange={(e) => { setStoreId(e.target.value || null); if (extraCls) closeMenu(); }}
>
<option value="">All Stores</option>
{stores.map((s) => <option key={s.storeId} value={s.storeId}>{s.storeName}</option>)}
</select>
);
return (
<nav className="navbar">
<nav className="fixed top-0 left-0 w-full bg-[#e68672] shadow-[0_2px_10px_rgba(0,0,0,0.1)] z-[1000] px-8 py-2 grid [grid-template-columns:1fr_auto_1fr] items-center min-h-[70px] max-[1100px]:px-4">
<Link href="/" onClick={closeMenu}>
<Image
className="mx-3"
src="/logo_simple.png"
alt="store_logo"
width={50}
height={50}
id="logo"
loading="eager"
/>
<Image className="mx-3" src="/logo_simple.png" alt="store_logo" width={50} height={50} id="logo" loading="eager" />
</Link>
{/* Desktop: inline links + auth */}
<div className="nav-links">
<Link href="/" className="nav-link">Home</Link>
<Link href="/adopt" className="nav-link">Adopt</Link>
<Link href="/products" className="nav-link">Store</Link>
<Link href="/appointments" className="nav-link">Appointments</Link>
<Link href="/ai-chat" className="nav-link">Help</Link>
<Link href="/contact" className="nav-link">Contact</Link>
{/*} <Link href="/about" className="nav-link">About</Link> */}
{/* Desktop nav links */}
<div className="hidden min-[1101px]:flex items-center gap-5 justify-center">
<Link href="/" className={navLinkCls}>Home</Link>
<Link href="/adopt" className={navLinkCls}>Adopt</Link>
<Link href="/products" className={navLinkCls}>Store</Link>
<Link href="/appointments" className={navLinkCls}>Appointments</Link>
<Link href="/ai-chat" className={navLinkCls}>Help</Link>
<Link href="/contact" className={navLinkCls}>Contact</Link>
</div>
<div className="nav-auth">
{user && stores.length > 0 && (
<select
className="nav-store-select"
value={selectedStoreId ?? ""}
onChange={(e) => setStoreId(e.target.value || null)}
>
<option value="">All Stores</option>
{stores.map((s) => (
<option key={s.storeId} value={s.storeId}>
{s.storeName}
</option>
))}
</select>
)}
{user && (
<Link href="/cart" className="nav-cart-btn" aria-label="Cart">
🛒
{itemCount > 0 && (
<span className="nav-cart-badge">{itemCount > 99 ? "99+" : itemCount}</span>
)}
</Link>
)}
{/* Desktop auth */}
<div className="hidden min-[1101px]:flex items-center gap-2 justify-self-end min-w-0 overflow-hidden whitespace-nowrap">
{storeSelect("mr-2 max-w-[160px]")}
{user && <CartIcon itemCount={itemCount} />}
{loading ? null : user ? (
<>
<Link href="/profile" className="nav-link nav-greeting">
<Link href="/profile" className={`${navLinkCls} font-bold`}>
Hello, {(user.fullName || user.username).split(" ")[0]}
</Link>
<button type="button" className="nav-logout-btn" onClick={handleLogout}>
<button type="button" className="bg-[rgba(47,47,47,0.15)] text-[#2f2f2f] border border-[rgba(47,47,47,0.4)] rounded-full px-4 py-[0.35rem] text-[0.95rem] cursor-pointer transition-colors whitespace-nowrap hover:bg-[rgba(47,47,47,0.25)]" onClick={handleLogout}>
Log Out
</button>
</>
) : (
<>
<Link href="/login" className="nav-link">Log In</Link>
<Link href="/register" className="nav-link nav-register-btn">Register</Link>
<Link href="/login" className={navLinkCls}>Log In</Link>
<Link href="/register" className="bg-[#2f2f2f] text-[#e68672] font-semibold rounded-full px-4 py-[0.4rem] no-underline transition-colors hover:bg-[#444]">Register</Link>
</>
)}
</div>
{/* Mobile: cart icon + hamburger always in topbar */}
<div className="nav-mobile-bar">
{user && (
<Link href="/cart" className="nav-cart-btn" aria-label="Cart" onClick={closeMenu}>
🛒
{itemCount > 0 && (
<span className="nav-cart-badge">{itemCount > 99 ? "99+" : itemCount}</span>
)}
</Link>
)}
{/* Mobile bar: cart + hamburger */}
<div className="flex min-[1101px]:hidden items-center gap-2 [grid-column:3] justify-self-end">
{user && <CartIcon itemCount={itemCount} onClick={closeMenu} />}
<button
className={`nav-hamburger${menuOpen ? " nav-hamburger--open" : ""}`}
className={`flex flex-col justify-center gap-[5px] w-9 h-9 bg-transparent border-none cursor-pointer p-1 rounded-md transition-colors hover:bg-[rgba(47,47,47,0.12)]${menuOpen ? " nav-hamburger--open" : ""}`}
aria-label="Toggle navigation menu"
aria-expanded={menuOpen}
onClick={() => setMenuOpen((o) => !o)}
>
<span />
<span />
<span />
<span className="block h-[2px] w-full bg-[#2f2f2f] rounded-sm transition-all duration-[250ms] origin-center" />
<span className="block h-[2px] w-full bg-[#2f2f2f] rounded-sm transition-all duration-[250ms] origin-center" />
<span className="block h-[2px] w-full bg-[#2f2f2f] rounded-sm transition-all duration-[250ms] origin-center" />
</button>
</div>
{/* Mobile dropdown drawer */}
{menuOpen && (
<div className="nav-drawer">
<Link href="/" className="nav-drawer-link" onClick={closeMenu}>Home</Link>
<Link href="/adopt" className="nav-drawer-link" onClick={closeMenu}>Adopt</Link>
<Link href="/products" className="nav-drawer-link" onClick={closeMenu}>Store</Link>
<Link href="/appointments" className="nav-drawer-link" onClick={closeMenu}>Appointments</Link>
<Link href="/ai-chat" className="nav-drawer-link" onClick={closeMenu}>Help</Link>
<Link href="/contact" className="nav-drawer-link" onClick={closeMenu}>Contact</Link>
{/* <Link href="/about" className="nav-drawer-link" onClick={closeMenu}>About</Link> */}
<div className="flex flex-col absolute top-[70px] left-0 w-full bg-[#e68672] px-6 pb-6 pt-4 z-[999] shadow-[0_6px_20px_rgba(0,0,0,0.15)] rounded-b-[10px] gap-1">
<Link href="/" className={drawerLinkCls} onClick={closeMenu}>Home</Link>
<Link href="/adopt" className={drawerLinkCls} onClick={closeMenu}>Adopt</Link>
<Link href="/products" className={drawerLinkCls} onClick={closeMenu}>Store</Link>
<Link href="/appointments" className={drawerLinkCls} onClick={closeMenu}>Appointments</Link>
<Link href="/ai-chat" className={drawerLinkCls} onClick={closeMenu}>Help</Link>
<Link href="/contact" className={drawerLinkCls} onClick={closeMenu}>Contact</Link>
<div className="nav-drawer-divider" />
<div className="h-px bg-[rgba(47,47,47,0.2)] my-2" />
{user && stores.length > 0 && (
<select
className="nav-store-select nav-store-select--drawer"
value={selectedStoreId ?? ""}
onChange={(e) => { setStoreId(e.target.value || null); closeMenu(); }}
>
<option value="">All Stores</option>
{stores.map((s) => (
<option key={s.storeId} value={s.storeId}>
{s.storeName}
</option>
))}
</select>
)}
{storeSelect("w-full mb-1")}
{loading ? null : user ? (
<>
<Link href="/profile" className="nav-drawer-link" onClick={closeMenu}>
<Link href="/profile" className={drawerLinkCls} onClick={closeMenu}>
My Profile ({user.fullName || user.username})
</Link>
<button type="button" className="nav-logout-btn nav-logout-btn--drawer" onClick={handleLogout}>
<button type="button" className="w-full bg-[rgba(47,47,47,0.15)] text-[#2f2f2f] border border-[rgba(47,47,47,0.4)] rounded-lg px-4 py-[0.65rem] text-base cursor-pointer transition-colors mt-1 hover:bg-[rgba(47,47,47,0.25)]" onClick={handleLogout}>
Log Out
</button>
</>
) : (
<>
<Link href="/login" className="nav-drawer-link" onClick={closeMenu}>Log In</Link>
<Link href="/register" className="nav-drawer-link nav-drawer-link--register" onClick={closeMenu}>Register</Link>
<Link href="/login" className={drawerLinkCls} onClick={closeMenu}>Log In</Link>
<Link href="/register" className="block mt-1 bg-[#2f2f2f] text-[#e68672] font-bold rounded-full px-4 py-[0.6rem] text-center no-underline transition-colors hover:bg-[#444]" onClick={closeMenu}>Register</Link>
</>
)}
</div>

View File

@@ -3,22 +3,22 @@ import { getStatusClass } from "@/components/petUtils";
export default function PetCard({petId, petName, petSpecies, petStatus, imageUrl}) {
return (
<Link href={`/adopt/${petId}`} className="pet-card">
<div className="pet-card-image-wrapper">
<Link href={`/adopt/${petId}`} className="no-underline text-inherit flex flex-col rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.08)] transition-all duration-300 hover:-translate-y-1.5 hover:shadow-[0_8px_24px_rgba(0,0,0,0.13)] bg-white">
<div className="bg-[#fff8ee] flex items-center justify-center aspect-square">
<img
src={imageUrl || "/images/pet-placeholder.png"}
alt={petName}
className="pet-card-image"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null;
e.currentTarget.src = "/images/pet-placeholder.png";
}}
/>
</div>
<div className="pet-card-body">
<h3 className="pet-card-name">{petName}</h3>
<p className="pet-card-species">{petSpecies}</p>
<span className={`pet-card-status ${getStatusClass(petStatus)}`}>
<div className="px-3 pt-[0.6rem] pb-3 flex flex-col gap-[0.2rem]">
<h3 className="text-[0.95rem] font-bold text-[#222] m-0 truncate">{petName}</h3>
<p className="text-[0.8rem] text-[#666] m-0">{petSpecies}</p>
<span className={`inline-block mt-[0.2rem] px-2 py-[0.15rem] rounded-full text-[0.7rem] font-semibold capitalize w-fit ${getStatusClass(petStatus)}`}>
{petStatus}
</span>
</div>

View File

@@ -1,14 +1,18 @@
import Link from "next/link";
import { getStatusClass } from "@/components/petUtils";
const fieldRowCls = "flex items-center px-5 py-[0.85rem] border-b border-[#eee] last:border-b-0";
const fieldLabelCls = "w-[140px] text-[0.9rem] font-semibold text-[#888] uppercase tracking-[0.04em] shrink-0";
const fieldValueCls = "text-base text-[#333]";
export default function PetProfile({ petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, imageUrl, storeId, storeName }) {
return (
<div className="pet-detail-card">
<div className="pet-detail-image-wrapper">
<div className="flex gap-12 bg-white rounded-2xl shadow-[0_6px_24px_rgba(0,0,0,0.1)] overflow-hidden max-[768px]:flex-col max-[768px]:gap-0">
<div className="shrink-0 w-[280px] bg-[#fff8ee] flex items-center justify-center max-[768px]:w-full max-[768px]:h-[200px]">
<img
src={imageUrl || "/images/pet-placeholder.png"}
alt={petName}
className="pet-detail-image"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null;
e.currentTarget.src = "/images/pet-placeholder.png";
@@ -16,46 +20,45 @@ export default function PetProfile({ petId, petName, petSpecies, petBreed, petAg
/>
</div>
<div className="pet-detail-info">
<div className="pet-detail-header">
<h1 className="pet-detail-name">{petName}</h1>
<span className={`pet-card-status ${getStatusClass(petStatus)}`}>
<div className="flex-1 py-10 pr-10 flex flex-col gap-6 max-[768px]:p-7">
<div className="flex items-center gap-4 flex-wrap">
<h1 className="text-[2.2rem] font-bold text-[#222] m-0">{petName}</h1>
<span className={`inline-block px-2 py-[0.15rem] rounded-full text-[0.7rem] font-semibold capitalize w-fit ${getStatusClass(petStatus)}`}>
{petStatus}
</span>
</div>
<div className="pet-detail-fields">
<div className="pet-detail-row">
<span className="pet-detail-label">Species</span>
<span className="pet-detail-value">{petSpecies ?? "—"}</span>
<div className="flex flex-col border border-[#eee] rounded-[10px] overflow-hidden">
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Species</span>
<span className={fieldValueCls}>{petSpecies ?? "—"}</span>
</div>
<div className="pet-detail-row">
<span className="pet-detail-label">Breed</span>
<span className="pet-detail-value">{petBreed ?? "—"}</span>
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Breed</span>
<span className={fieldValueCls}>{petBreed ?? "—"}</span>
</div>
<div className="pet-detail-row">
<span className="pet-detail-label">Age</span>
<span className="pet-detail-value">
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Age</span>
<span className={fieldValueCls}>
{petAge != null ? `${petAge} ${petAge === 1 ? "year" : "years"}` : "—"}
</span>
</div>
<div className="pet-detail-row">
<span className="pet-detail-label">Adoption Fee</span>
<span className="pet-detail-value pet-detail-price">
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Adoption Fee</span>
<span className={`${fieldValueCls} font-bold text-[#1a7a3c] text-[1.1rem]`}>
{petPrice != null ? `$${parseFloat(petPrice).toFixed(2)}` : "—"}
</span>
</div>
</div>
{/* Status */}
{petStatus?.toLowerCase() === "available" && (
<div className="pet-detail-cta">
<p className="pet-detail-cta-text">
<div className="bg-[#fff8ee] rounded-xl p-5">
<p className="text-[0.95rem] text-[#555] m-0 mb-4">
Interested in adopting {petName}? Visit us in store or schedule an appointment.
</p>
<Link
href={`/appointments?adoptionMode=true&petId=${petId}&petName=${encodeURIComponent(petName || "")}&petSpecies=${encodeURIComponent(petSpecies || "")}&petBreed=${encodeURIComponent(petBreed || "")}${storeId ? `&storeId=${storeId}` : ""}${storeName ? `&storeName=${encodeURIComponent(storeName)}` : ""}`}
className="pet-detail-cta-btn"
className="inline-block px-6 py-[0.65rem] bg-[#e68672] text-white no-underline rounded-lg text-[0.95rem] font-semibold transition-colors hover:bg-[#d4705e]"
>
Schedule an Appointment
</Link>

View File

@@ -6,6 +6,8 @@ import { useRouter } from "next/navigation";
import { useAuth } from "@/context/AuthContext";
import { useCart } from "@/context/CartContext";
const qtyBtnCls = "w-7 h-7 border border-[#ddd] rounded-md bg-white text-base cursor-pointer flex items-center justify-center transition-colors hover:border-[#e68672] disabled:opacity-50";
export default function ProductCard({ prodId, prodName, categoryName, prodPrice, imageUrl }) {
const { user } = useAuth();
const { addItem, selectedStoreId } = useCart();
@@ -16,10 +18,7 @@ export default function ProductCard({ prodId, prodName, categoryName, prodPrice,
async function handleAddToCart(e) {
e.preventDefault();
if (!user) {
router.push("/login");
return;
}
if (!user) { router.push("/login"); return; }
if (!selectedStoreId) {
setFeedback("Please select a store first");
setTimeout(() => setFeedback(null), 2500);
@@ -40,57 +39,43 @@ export default function ProductCard({ prodId, prodName, categoryName, prodPrice,
}
return (
<div className="pet-card product-card-wrapper">
<Link href={`/products/${prodId}`} className="product-card-link">
<div className="pet-card-image-wrapper">
<div className="flex flex-col no-underline rounded-2xl overflow-hidden shadow-[0_4px_12px_rgba(0,0,0,0.08)] transition-all duration-300 hover:-translate-y-1.5 hover:shadow-[0_8px_24px_rgba(0,0,0,0.13)] bg-white">
<Link href={`/products/${prodId}`} className="no-underline text-inherit flex-1">
<div className="bg-[#fff8ee] flex items-center justify-center aspect-square">
<img
src={imageUrl || "/images/pet-placeholder.png"}
alt={prodName}
className="pet-card-image"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null;
e.currentTarget.src = "/images/pet-placeholder.png";
}}
/>
</div>
<div className="pet-card-body">
<h3 className="pet-card-name">{prodName}</h3>
<p className="pet-card-species">{categoryName}</p>
<div className="px-3 pt-[0.6rem] pb-3 flex flex-col gap-[0.2rem]">
<h3 className="text-[0.95rem] font-bold text-[#222] m-0 truncate">{prodName}</h3>
<p className="text-[0.8rem] text-[#666] m-0">{categoryName}</p>
{prodPrice != null && (
<span className="product-card-price">${parseFloat(prodPrice).toFixed(2)}</span>
<span className="inline-block mt-1 text-[1.05rem] font-bold text-[#1a7a3c]">${parseFloat(prodPrice).toFixed(2)}</span>
)}
</div>
</Link>
<div className="product-card-actions">
<div className="product-card-qty-row">
<button
className="product-card-qty-btn"
type="button"
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
disabled={adding}
>
</button>
<span className="product-card-qty-val">{quantity}</span>
<button
className="product-card-qty-btn"
type="button"
onClick={() => setQuantity((q) => q + 1)}
disabled={adding}
>
+
</button>
<div className="px-3 pb-3 flex flex-col gap-1.5">
<div className="flex items-center gap-2">
<button className={qtyBtnCls} type="button" onClick={() => setQuantity((q) => Math.max(1, q - 1))} disabled={adding}></button>
<span className="min-w-6 text-center font-semibold text-[0.9rem]">{quantity}</span>
<button className={qtyBtnCls} type="button" onClick={() => setQuantity((q) => q + 1)} disabled={adding}>+</button>
</div>
<button
className="product-card-add-btn"
className="w-full py-[0.45rem] bg-[#e68672] text-white border-none rounded-lg text-[0.88rem] font-bold cursor-pointer transition-colors hover:bg-[#d4705e] disabled:opacity-60 disabled:cursor-not-allowed"
type="button"
onClick={handleAddToCart}
disabled={adding}
>
{adding ? "Adding…" : "Add to Cart"}
</button>
{feedback && <p className="product-card-feedback">{feedback}</p>}
{feedback && <p className="text-[0.78rem] text-[#2e7d32] m-0 text-center">{feedback}</p>}
</div>
</div>
);

View File

@@ -4,17 +4,20 @@ import { useState } from "react";
import { useAuth } from "@/context/AuthContext";
import { useCart } from "@/context/CartContext";
const fieldRowCls = "flex items-center px-5 py-[0.85rem] border-b border-[#eee] last:border-b-0";
const fieldLabelCls = "w-[140px] text-[0.9rem] font-semibold text-[#888] uppercase tracking-[0.04em] shrink-0";
const fieldValueCls = "text-base text-[#333]";
const qtyBtnCls = "w-7 h-7 border border-[#ddd] rounded-md bg-white text-base cursor-pointer flex items-center justify-center transition-colors hover:border-[#e68672] disabled:opacity-50";
export default function ProductProfile({ prodId, prodName, categoryName, prodDesc, prodPrice, imageUrl }) {
const { user } = useAuth();
const { addItem, selectedStoreId } = useCart();
const [quantity, setQuantity] = useState(1);
const [adding, setAdding] = useState(false);
const [feedback, setFeedback] = useState(null); // { type: "success"|"error", message }
const [feedback, setFeedback] = useState(null);
function changeQty(delta) {
setQuantity((q) => Math.max(1, q + delta));
}
function changeQty(delta) { setQuantity((q) => Math.max(1, q + delta)); }
async function handleAddToCart() {
setAdding(true);
@@ -31,12 +34,12 @@ export default function ProductProfile({ prodId, prodName, categoryName, prodDes
}
return (
<div className="pet-detail-card">
<div className="pet-detail-image-wrapper">
<div className="flex gap-12 bg-white rounded-2xl shadow-[0_6px_24px_rgba(0,0,0,0.1)] overflow-hidden max-[768px]:flex-col max-[768px]:gap-0">
<div className="shrink-0 w-[280px] bg-[#fff8ee] flex items-center justify-center max-[768px]:w-full max-[768px]:h-[200px]">
<img
src={imageUrl || "/images/pet-placeholder.png"}
alt={prodName}
className="pet-detail-image"
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null;
e.currentTarget.src = "/images/pet-placeholder.png";
@@ -44,64 +47,50 @@ export default function ProductProfile({ prodId, prodName, categoryName, prodDes
/>
</div>
<div className="pet-detail-info">
<div className="pet-detail-header">
<h1 className="pet-detail-name">{prodName}</h1>
<div className="flex-1 py-10 pr-10 flex flex-col gap-6 max-[768px]:p-7">
<div className="flex items-center gap-4 flex-wrap">
<h1 className="text-[2.2rem] font-bold text-[#222] m-0">{prodName}</h1>
</div>
<div className="pet-detail-fields">
<div className="pet-detail-row">
<span className="pet-detail-label">Category</span>
<span className="pet-detail-value">{categoryName ?? "—"}</span>
<div className="flex flex-col border border-[#eee] rounded-[10px] overflow-hidden">
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Category</span>
<span className={fieldValueCls}>{categoryName ?? "—"}</span>
</div>
<div className="pet-detail-row">
<span className="pet-detail-label">Price</span>
<span className="pet-detail-value pet-detail-price">
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Price</span>
<span className={`${fieldValueCls} font-bold text-[#1a7a3c] text-[1.1rem]`}>
{prodPrice != null ? `$${parseFloat(prodPrice).toFixed(2)}` : "—"}
</span>
</div>
<div className="pet-detail-row">
<span className="pet-detail-label">Description</span>
<span className="pet-detail-value">{prodDesc ?? "—"}</span>
<div className={fieldRowCls}>
<span className={fieldLabelCls}>Description</span>
<span className={fieldValueCls}>{prodDesc ?? "—"}</span>
</div>
</div>
<div className="pet-detail-cta">
<div className="bg-[#fff8ee] rounded-xl p-5">
{!user ? (
<p className="pet-detail-cta-text">
<a href="/login" className="appt-link">Log in</a> to purchase this item.
<p className="text-[0.95rem] text-[#555] m-0">
<a href="/login" className="text-[#e68672] font-semibold no-underline hover:underline">Log in</a> to purchase this item.
</p>
) : !selectedStoreId ? (
<p className="pet-detail-cta-text">
<p className="text-[0.95rem] text-[#555] m-0">
Select a store from the navigation bar to add items to your cart.
</p>
) : (
<>
<div className="product-qty-row">
<span className="product-qty-label">Quantity</span>
<div className="product-qty-controls">
<button
className="cart-qty-btn"
onClick={() => changeQty(-1)}
disabled={quantity <= 1 || adding}
aria-label="Decrease quantity"
>
</button>
<span className="cart-qty-val">{quantity}</span>
<button
className="cart-qty-btn"
onClick={() => changeQty(1)}
disabled={adding}
aria-label="Increase quantity"
>
+
</button>
<div className="flex items-center gap-4 mb-4">
<span className="text-[0.95rem] font-semibold text-[#555]">Quantity</span>
<div className="flex items-center gap-2">
<button className={qtyBtnCls} onClick={() => changeQty(-1)} disabled={quantity <= 1 || adding} aria-label="Decrease quantity"></button>
<span className="min-w-7 text-center font-semibold">{quantity}</span>
<button className={qtyBtnCls} onClick={() => changeQty(1)} disabled={adding} aria-label="Increase quantity">+</button>
</div>
</div>
<button
className="product-add-to-cart-btn"
className="w-full py-[0.85rem] bg-[#e68672] text-[#2f2f2f] border-none rounded-[10px] text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] hover:text-white active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed"
onClick={handleAddToCart}
disabled={adding}
>
@@ -109,7 +98,7 @@ export default function ProductProfile({ prodId, prodName, categoryName, prodDes
</button>
{feedback && (
<p className={`product-cart-feedback product-cart-feedback--${feedback.type}`}>
<p className={`mt-3 text-[0.9rem] rounded-lg px-4 py-[0.6rem] ${feedback.type === "success" ? "bg-[#f0fff4] border border-[#b2dfdb] text-[#1a7a3c]" : "bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b]"}`}>
{feedback.message}
</p>
)}