Files
Shifted/app/cart/page.tsx
2026-02-10 01:14:19 +00:00

227 lines
6.9 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import DesktopHeader from "../../components/DesktopHeader";
import MobileHeader from "../../components/MobileHeader";
import Footer from "../../components/Footer";
type CartItem = {
id: string;
name: string;
unitAmount: number;
currency: string;
quantity: number;
};
type Cart = {
id: string;
items: CartItem[];
};
export default function CartPage() {
const [slug, setSlug] = useState("storeshifted");
const [cart, setCart] = useState<Cart | null>(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState("");
const { data: session, status } = useSession();
const isAuthenticated = status === "authenticated";
const [addressTo, setAddressTo] = useState({
name: "",
street1: "",
city: "",
state: "",
zip: "",
country: "US",
phone: "",
});
useEffect(() => {
const stored = window.localStorage.getItem("connectStoreSlug");
if (stored) setSlug(stored);
}, []);
async function loadCart(currentSlug: string) {
setLoading(true);
setMessage("");
try {
const res = await fetch(`/api/cart/get?slug=${currentSlug}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to load cart");
setCart(data.cart);
} catch (err: any) {
setMessage(err.message || "Failed to load cart.");
} finally {
setLoading(false);
}
}
useEffect(() => {
loadCart(slug);
}, [slug]);
async function updateQty(itemId: string, quantity: number) {
setLoading(true);
try {
const res = await fetch("/api/cart/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ itemId, quantity }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to update cart");
await loadCart(slug);
} catch (err: any) {
setMessage(err.message || "Failed to update cart.");
} finally {
setLoading(false);
}
}
async function removeItem(itemId: string) {
setLoading(true);
try {
const res = await fetch("/api/cart/remove", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ itemId }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to remove item");
await loadCart(slug);
} catch (err: any) {
setMessage(err.message || "Failed to remove item.");
} finally {
setLoading(false);
}
}
async function checkout() {
setLoading(true);
setMessage("");
try {
const res = await fetch("/api/cart/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
slug,
shippingAddress: addressTo,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Checkout failed");
window.location.href = data.url;
} catch (err: any) {
setMessage(err.message || "Checkout failed.");
} finally {
setLoading(false);
}
}
const total =
cart?.items.reduce((sum, item) => sum + item.unitAmount * item.quantity, 0) ?? 0;
return (
<main className="cart-page">
<DesktopHeader />
<MobileHeader />
<section className="section">
<div className="container cart-page__inner">
<h1 className="page-title">Cart</h1>
{isAuthenticated ? (
<p className="connect-muted">Signed in as {session!.user.email}</p>
) : (
<p className="connect-muted">
Sign in to save your cart across devices. <a href="/login">Sign in</a>
</p>
)}
{message ? <div className="connect-message">{message}</div> : null}
<div className="cart-list">
{cart?.items.map((item) => (
<div key={item.id} className="cart-item">
<div className="cart-item__info">
<div className="cart-item__name">{item.name}</div>
<div className="cart-item__price">
{(item.unitAmount / 100).toFixed(2)} {item.currency.toUpperCase()}
</div>
</div>
<div className="cart-item__actions">
<input
type="number"
min={1}
value={item.quantity}
onChange={(e) => updateQty(item.id, Number(e.target.value))}
/>
<button className="btn btn--ghost" onClick={() => removeItem(item.id)}>
Remove
</button>
</div>
</div>
))}
{!cart?.items.length ? (
<div className="storefront-muted">Your cart is empty.</div>
) : null}
</div>
<div className="cart-shipping">
<h2>Shipping</h2>
<div className="cart-shipping__grid">
<div className="cart-shipping__card">
<h3>Ship To</h3>
<input
value={addressTo.name}
onChange={(e) => setAddressTo({ ...addressTo, name: e.target.value })}
placeholder="Name"
/>
<input
value={addressTo.street1}
onChange={(e) =>
setAddressTo({ ...addressTo, street1: e.target.value })
}
placeholder="Street"
/>
<input
value={addressTo.city}
onChange={(e) => setAddressTo({ ...addressTo, city: e.target.value })}
placeholder="City"
/>
<input
value={addressTo.state}
onChange={(e) =>
setAddressTo({ ...addressTo, state: e.target.value })
}
placeholder="State/Province"
/>
<input
value={addressTo.zip}
onChange={(e) => setAddressTo({ ...addressTo, zip: e.target.value })}
placeholder="Postal Code"
/>
<input
value={addressTo.country}
onChange={(e) =>
setAddressTo({ ...addressTo, country: e.target.value })
}
placeholder="Country"
/>
</div>
</div>
</div>
<div className="cart-summary">
<div>
Total: {(total / 100).toFixed(2)} USD
</div>
<button className="btn" onClick={checkout} disabled={loading || !cart?.items.length}>
Checkout
</button>
</div>
</div>
</section>
<Footer />
</main>
);
}