Cart fixes (backend), adjusted header, added footer, mobile formatting updates
This commit is contained in:
57
web/components/Footer.js
Normal file
57
web/components/Footer.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
<div className="footer-container">
|
||||
|
||||
<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">
|
||||
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>
|
||||
</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>
|
||||
</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>
|
||||
<li>123 Street Street, Calgary, Alberta, Canada</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="footer-bottom">
|
||||
<p>© {new Date().getFullYear()} Leon's Pet Store. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export default function DisplayNav() {
|
||||
const { itemCount, selectedStoreId, setStoreId } = useCart();
|
||||
const router = useRouter();
|
||||
const [stores, setStores] = useState([]);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
@@ -26,27 +27,35 @@ export default function DisplayNav() {
|
||||
function handleLogout() {
|
||||
logout();
|
||||
router.push("/");
|
||||
setMenuOpen(false);
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="navbar">
|
||||
<Image
|
||||
className="mx-3"
|
||||
src="/logo_simple.png"
|
||||
alt="store_logo"
|
||||
width={50}
|
||||
height={50}
|
||||
id="logo"
|
||||
/>
|
||||
<Link href="/" onClick={closeMenu}>
|
||||
<Image
|
||||
className="mx-3"
|
||||
src="/logo_simple.png"
|
||||
alt="store_logo"
|
||||
width={50}
|
||||
height={50}
|
||||
id="logo"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Desktop: inline links + auth */}
|
||||
<div className="nav-links">
|
||||
<Link href="/" className="nav-link">Home</Link>
|
||||
<Link href="/adopt" className="nav-link">Adopt a Pet</Link>
|
||||
<Link href="/products" className="nav-link">Online Store</Link>
|
||||
<Link href="/appointments" className="nav-link">Schedule an Appointment</Link>
|
||||
<Link href="/ai-chat" className="nav-link">AI Assistant</Link>
|
||||
<Link href="/contact" className="nav-link">Contact Us</Link>
|
||||
<Link href="/about" className="nav-link">About Us</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>
|
||||
</div>
|
||||
|
||||
<div className="nav-auth">
|
||||
@@ -77,7 +86,7 @@ export default function DisplayNav() {
|
||||
{loading ? null : user ? (
|
||||
<>
|
||||
<Link href="/profile" className="nav-link nav-greeting">
|
||||
Hello, {user.fullName || user.username}
|
||||
Hello, {(user.fullName || user.username).split(" ")[0]}
|
||||
</Link>
|
||||
<button type="button" className="nav-logout-btn" onClick={handleLogout}>
|
||||
Log Out
|
||||
@@ -90,6 +99,74 @@ export default function DisplayNav() {
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
<button
|
||||
className={`nav-hamburger${menuOpen ? " nav-hamburger--open" : ""}`}
|
||||
aria-label="Toggle navigation menu"
|
||||
aria-expanded={menuOpen}
|
||||
onClick={() => setMenuOpen((o) => !o)}
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</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="nav-drawer-divider" />
|
||||
|
||||
{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>
|
||||
)}
|
||||
|
||||
{loading ? null : user ? (
|
||||
<>
|
||||
<Link href="/profile" className="nav-drawer-link" onClick={closeMenu}>
|
||||
My Profile ({user.fullName || user.username})
|
||||
</Link>
|
||||
<button type="button" className="nav-logout-btn nav-logout-btn--drawer" 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>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,35 @@
|
||||
import Link from "next/link";
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
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 }
|
||||
|
||||
function changeQty(delta) {
|
||||
setQuantity((q) => Math.max(1, q + delta));
|
||||
}
|
||||
|
||||
async function handleAddToCart() {
|
||||
setAdding(true);
|
||||
setFeedback(null);
|
||||
try {
|
||||
await addItem(prodId, quantity);
|
||||
setFeedback({ type: "success", message: `${quantity} × ${prodName} added to cart!` });
|
||||
setQuantity(1);
|
||||
} catch (err) {
|
||||
setFeedback({ type: "error", message: err.message ?? "Failed to add to cart." });
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
}
|
||||
|
||||
export default function ProductProfile({ prodName, categoryName, prodDesc, prodPrice, imageUrl }) {
|
||||
return (
|
||||
<div className="pet-detail-card">
|
||||
<div className="pet-detail-image-wrapper">
|
||||
@@ -36,6 +65,57 @@ export default function ProductProfile({ prodName, categoryName, prodDesc, prodP
|
||||
<span className="pet-detail-value">{prodDesc ?? "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pet-detail-cta">
|
||||
{!user ? (
|
||||
<p className="pet-detail-cta-text">
|
||||
<a href="/login" className="appt-link">Log in</a> to purchase this item.
|
||||
</p>
|
||||
) : !selectedStoreId ? (
|
||||
<p className="pet-detail-cta-text">
|
||||
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>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="product-add-to-cart-btn"
|
||||
onClick={handleAddToCart}
|
||||
disabled={adding}
|
||||
>
|
||||
{adding ? "Adding…" : "Add to Cart"}
|
||||
</button>
|
||||
|
||||
{feedback && (
|
||||
<p className={`product-cart-feedback product-cart-feedback--${feedback.type}`}>
|
||||
{feedback.message}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user