Stripe Payment
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { AuthProvider } from "@/context/AuthContext";
|
||||
import { CartProvider } from "@/context/CartContext";
|
||||
|
||||
export default function ClientProviders({children}) {
|
||||
return <AuthProvider>{children}</AuthProvider>;
|
||||
export default function ClientProviders({ children }) {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<CartProvider>{children}</CartProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,25 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
export default function DisplayNav() {
|
||||
const {user, logout, loading} = useAuth();
|
||||
const { user, token, logout, loading } = useAuth();
|
||||
const { itemCount, selectedStoreId, setStoreId } = useCart();
|
||||
const router = useRouter();
|
||||
const [stores, setStores] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
fetch("/api/v1/stores?size=100", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data) => { if (data) setStores(data.content ?? []); })
|
||||
.catch(() => {});
|
||||
}, [token]);
|
||||
|
||||
function handleLogout() {
|
||||
logout();
|
||||
@@ -16,12 +30,14 @@ export default function DisplayNav() {
|
||||
|
||||
return (
|
||||
<nav className="navbar">
|
||||
<Image className="mx-3"
|
||||
<Image
|
||||
className="mx-3"
|
||||
src="/logo_simple.png"
|
||||
alt="store_logo"
|
||||
width={50}
|
||||
height={50}
|
||||
id="logo"/>
|
||||
id="logo"
|
||||
/>
|
||||
|
||||
<div className="nav-links">
|
||||
<Link href="/" className="nav-link">Home</Link>
|
||||
@@ -33,6 +49,30 @@ export default function DisplayNav() {
|
||||
</div>
|
||||
|
||||
<div className="nav-auth">
|
||||
{stores.length > 0 && (
|
||||
<select
|
||||
className="nav-store-select"
|
||||
value={selectedStoreId ?? ""}
|
||||
onChange={(e) => setStoreId(e.target.value || null)}
|
||||
>
|
||||
<option value="">Select Store</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>
|
||||
)}
|
||||
|
||||
{loading ? null : user ? (
|
||||
<>
|
||||
<Link href="/profile" className="nav-link nav-greeting">
|
||||
|
||||
@@ -1,26 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
|
||||
export default function ProductCard({ prodId, prodName, categoryName, prodPrice, imageUrl }) {
|
||||
const { user } = useAuth();
|
||||
const { addItem, selectedStoreId } = useCart();
|
||||
const router = useRouter();
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [feedback, setFeedback] = useState(null);
|
||||
|
||||
async function handleAddToCart(e) {
|
||||
e.preventDefault();
|
||||
if (!user) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
if (!selectedStoreId) {
|
||||
setFeedback("Please select a store first");
|
||||
setTimeout(() => setFeedback(null), 2500);
|
||||
return;
|
||||
}
|
||||
setAdding(true);
|
||||
setFeedback(null);
|
||||
try {
|
||||
await addItem(prodId, quantity);
|
||||
setFeedback("Added!");
|
||||
setTimeout(() => setFeedback(null), 1500);
|
||||
} catch (err) {
|
||||
setFeedback(err.message || "Failed to add");
|
||||
setTimeout(() => setFeedback(null), 2500);
|
||||
} finally {
|
||||
setAdding(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={`/products/${prodId}`} className="pet-card">
|
||||
<div className="pet-card-image-wrapper">
|
||||
<img
|
||||
src={imageUrl || "/images/pet-placeholder.png"}
|
||||
alt={prodName}
|
||||
className="pet-card-image"
|
||||
onError={(e) => {
|
||||
e.currentTarget.onerror = null;
|
||||
e.currentTarget.src = "/images/pet-placeholder.png";
|
||||
}}
|
||||
/>
|
||||
<div className="pet-card product-card-wrapper">
|
||||
<Link href={`/products/${prodId}`} className="product-card-link">
|
||||
<div className="pet-card-image-wrapper">
|
||||
<img
|
||||
src={imageUrl || "/images/pet-placeholder.png"}
|
||||
alt={prodName}
|
||||
className="pet-card-image"
|
||||
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>
|
||||
{prodPrice != null && (
|
||||
<span className="product-card-price">${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>
|
||||
<button
|
||||
className="product-card-add-btn"
|
||||
type="button"
|
||||
onClick={handleAddToCart}
|
||||
disabled={adding}
|
||||
>
|
||||
{adding ? "Adding…" : "Add to Cart"}
|
||||
</button>
|
||||
{feedback && <p className="product-card-feedback">{feedback}</p>}
|
||||
</div>
|
||||
<div className="pet-card-body">
|
||||
<h3 className="pet-card-name">{prodName}</h3>
|
||||
<p className="pet-card-species">{categoryName}</p>
|
||||
{prodPrice != null && (
|
||||
<span className="product-card-price">${parseFloat(prodPrice).toFixed(2)}</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user