// Author: Shiv // Date: April 2026 "use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; import { useCart } from "@/context/CartContext"; import { loadStripe } from "@stripe/stripe-js"; import { Elements, PaymentElement, useStripe, useElements, } from "@stripe/react-stripe-js"; import { apiCompleteCheckout } from "@/lib/cartApi"; //Initializes Stripe with the publishable key const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || ""); //Stripe payment form shown after the user clicks checkout function PaymentForm({ clientSecret, totalAmount, onSuccess, onCancel }) { const stripe = useStripe(); const elements = useElements(); const { token } = useAuth(); const [paying, setPaying] = useState(false); const [payError, setPayError] = useState(null); //Confirms the payment with Stripe, then tells the backend to complete the order async function handlePay(e) { e.preventDefault(); if (!stripe || !elements) return; setPaying(true); setPayError(null); const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: window.location.origin + "/cart/confirmation" }, redirect: "if_required", }); if (error) { setPayError(error.message); setPaying(false); } else { const paymentIntentId = clientSecret.split("_secret_")[0]; try { await apiCompleteCheckout(token, paymentIntentId); } catch { setPayError("Order confirmation failed. Please contact support."); setPaying(false); return; } onSuccess(); } } return (

Payment Details

Total to pay: ${parseFloat(totalAmount).toFixed(2)}

{payError &&

{payError}

}
); } //Cart page - shows items, coupons, loyalty points, order summary, and checkout export default function CartPage() { const { user, loading: authLoading, refreshUser } = useAuth(); const { cart, cartLoading, cartError, selectedStoreId, updateItem, removeItem, clearCart, applyCoupon, applyPoints, removeCoupon, checkout, cancelCheckout, } = useCart(); const router = useRouter(); const [couponInput, setCouponInput] = useState(""); const [couponError, setCouponError] = useState(null); const [couponSuccess, setCouponSuccess] = useState(null); const [couponLoading, setCouponLoading] = useState(false); const [pointsLoading, setPointsLoading] = useState(false); const [pointsError, setPointsError] = useState(null); const [optimisticPointsApplied, setOptimisticPointsApplied] = useState(null); const [checkoutLoading, setCheckoutLoading] = useState(false); const [checkoutError, setCheckoutError] = useState(null); const [clientSecret, setClientSecret] = useState(null); const [checkoutTotal, setCheckoutTotal] = useState(null); const [confirmed, setConfirmed] = useState(false); const [localQuantities, setLocalQuantities] = useState({}); //Redirect unauthenticated users to login useEffect(() => { if (!authLoading && !user) { router.push("/login"); } }, [authLoading, user, router]); //Sync local quantity inputs whenever the cart updates from the server useEffect(() => { if (cart?.items) { const map = {}; cart.items.forEach((i) => (map[i.cartItemId] = i.quantity)); setLocalQuantities(map); } setOptimisticPointsApplied(null); }, [cart]); //Cancel any leftover pending checkout if the page loads without a client secret useEffect(() => { if (cart?.checkoutPending && !clientSecret) { cancelCheckout().catch(() => {}); } }, [cart?.checkoutPending, clientSecret, cancelCheckout]); //Updates item quantity and rolls back the change if the request fails async function handleQuantityChange(cartItemId, newQty) { if (newQty < 1) return; setLocalQuantities((prev) => ({ ...prev, [cartItemId]: newQty })); try { await updateItem(cartItemId, newQty); } catch { if (cart?.items) { const original = cart.items.find((i) => i.cartItemId === cartItemId); if (original) { setLocalQuantities((prev) => ({ ...prev, [cartItemId]: original.quantity })); } } } } async function handleRemove(cartItemId) { try { await removeItem(cartItemId); } catch {} } //Applies the typed coupon code and shows the discount type and amount async function handleApplyCoupon() { if (!couponInput.trim()) return; setCouponLoading(true); setCouponError(null); setCouponSuccess(null); try { const updated = await applyCoupon(couponInput.trim()); setCouponInput(""); const dtype = updated.couponDiscountType?.toUpperCase(); const discountLabel = (dtype === "PERCENTAGE" || dtype === "PERCENT") && updated.couponDiscountValue != null ? `${parseFloat(updated.couponDiscountValue)}% off` : (dtype === "FIXED" || dtype === "FLAT") && updated.couponDiscountValue != null ? `$${parseFloat(updated.couponDiscountValue).toFixed(2)} off` : ""; setCouponSuccess(`Coupon "${updated.couponCode}" applied${discountLabel ? ` (${discountLabel})` : ""}!`); } catch (err) { setCouponError(err.message); } finally { setCouponLoading(false); } } async function handleTogglePoints(checked) { setOptimisticPointsApplied(checked); setPointsLoading(true); setPointsError(null); try { await applyPoints(checked); } catch (err) { setOptimisticPointsApplied(null); setPointsError(err.message || "Failed to apply loyalty points."); } finally { setPointsLoading(false); } } async function handleRemoveCoupon() { setCouponLoading(true); setCouponError(null); setCouponSuccess(null); try { await removeCoupon(); } catch (err) { setCouponError(err.message); } finally { setCouponLoading(false); } } //Starts checkout //Either gets a Stripe client secret for payment or marks the order complete directly async function handleCheckout() { if (!cart?.items?.length) return; setCheckoutLoading(true); setCheckoutError(null); try { const result = await checkout(); if (result?.clientSecret) { setClientSecret(result.clientSecret); setCheckoutTotal(result.totalAmount); } else if (result?.status === "succeeded") { refreshUser().catch(() => {}); setConfirmed(true); } } catch (err) { setCheckoutError(err.message); } finally { setCheckoutLoading(false); } } if (authLoading || cartLoading) { return (

Loading…

); } if (!user) return null; if (confirmed) { return (

Order Confirmed!

Thank you for your purchase. Your order has been placed successfully.

); } if (!selectedStoreId) { return (

Please select a store from the navigation bar to view your cart.

); } const items = cart?.items ?? []; return (

Your Cart

{cartError &&

{cartError}

} {items.length === 0 && !cartError && (

Your cart is empty.

)} {items.length > 0 && (
{/* Items list */}
{items.map((item) => (
{item.prodName} { e.currentTarget.onerror = null; e.currentTarget.src = "/images/pet-placeholder.png"; }} />

{item.prodName}

${parseFloat(item.unitPrice).toFixed(2)} each

{localQuantities[item.cartItemId] ?? item.quantity}

${(parseFloat(item.unitPrice) * (localQuantities[item.cartItemId] ?? item.quantity)).toFixed(2)}

))}
{/* Summary aside */}
)}
); }