"use client"; import Image from "next/image"; import { useCallback, useEffect, useState } from "react"; import { ExtendedArtefact } from "@appTypes/ApiTypes"; import { Currency } from "@appTypes/StoreModel"; import BottomFooter from "@components/BottomFooter"; import { useStoreState } from "@hooks/store"; export default function Shop() { const [artefacts, setArtefacts] = useState([]); const [hiddenArtefactIds, setHiddenArtefactIds] = useState([]); const [loading, setLoading] = useState(true); const [cart, setCart] = useState([]); const [showCartModal, setShowCartModal] = useState(false); const user = useStoreState((state) => state.user); useEffect(() => { async function fetchArtefacts() { setLoading(true); try { const res = await fetch("/api/artefacts"); const data = await res.json(); const transformed = data.artefact.map((a: any) => ({ id: a.id, name: a.name, description: a.description, location: a.warehouseArea, earthquakeID: a.earthquakeId?.toString() ?? "", observatory: a.type ?? "", dateReleased: a.createdAt ? new Date(a.createdAt).toLocaleDateString() : "", image: "/artefactImages/" + (a.imageName || "NoImageFound.PNG"), price: a.shopPrice ?? 100, })); setArtefacts(transformed); } catch (e) { console.error("Failed to fetch artefacts", e); } finally { setLoading(false); } } fetchArtefacts(); }, []); const [currentPage, setCurrentPage] = useState(1); const [selectedArtefact, setSelectedArtefact] = useState(null); const [showPaymentModal, setShowPaymentModal] = useState(false); const [artefactToBuy, setArtefactToBuy] = useState(null); const [cartCheckout, setCartCheckout] = useState(false); // true = checkout cart (not single artefact) const [showThankYouModal, setShowThankYouModal] = useState(false); const [orderNumber, setOrderNumber] = useState(null); const artefactsPerPage = 12; const indexOfLastArtefact = currentPage * artefactsPerPage; const indexOfFirstArtefact = indexOfLastArtefact - artefactsPerPage; const currentArtefacts = artefacts.slice(indexOfFirstArtefact, indexOfLastArtefact); const selectedCurrency = useStoreState((state) => state.currency.selectedCurrency); const conversionRates = useStoreState((state) => state.currency.conversionRates); const currencyTickers = useStoreState((state) => state.currency.tickers); const convertPrice = useCallback( (price: number, currency: Currency) => (price * conversionRates[currency]).toFixed(2), [conversionRates] ); const handleNextPage = () => { if (indexOfLastArtefact < artefacts.length) setCurrentPage((prev) => prev + 1); }; const handlePreviousPage = () => { if (currentPage > 1) setCurrentPage((prev) => prev - 1); }; function ArtefactCard({ artefact }: { artefact: ExtendedArtefact }) { return (
setSelectedArtefact(artefact)} > {artefact.name}

{artefact.name}

{artefact.location}

{artefact.earthquakeID}

{currencyTickers[selectedCurrency]} {convertPrice(artefact.price, selectedCurrency)}

); } function Modal({ artefact }: { artefact: ExtendedArtefact }) { if (!artefact) return null; const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) setSelectedArtefact(null); }; const inCart = cart.some((a) => a.id === artefact.id); return (

{artefact.name}

{artefact.name}

{currencyTickers[selectedCurrency]} {convertPrice(artefact.price, selectedCurrency)}

{artefact.description}

Location: {artefact.location}

{artefact.earthquakeID}

{artefact.observatory}

{artefact.dateReleased}

); } function CartModal() { const total = cart.reduce((sum, art) => sum + art.price, 0); const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) setShowCartModal(false); }; return (

Your Cart

{cart.length === 0 ? (

Your cart is empty.

) : ( <>
    {cart.map((art) => (
  • {art.name}

    {art.name}

    {art.location}

    {currencyTickers[selectedCurrency]} {convertPrice(art.price, selectedCurrency)}

  • ))}
Total: {currencyTickers[selectedCurrency]} {convertPrice(total, selectedCurrency)}
)}
); } function PaymentModal({ artefact, onClose, cartItems, }: { artefact?: ExtendedArtefact; onClose: () => void; cartItems?: ExtendedArtefact[]; }) { const [cardNumber, setCardNumber] = useState(""); const [expiry, setExpiry] = useState(""); const [cvc, setCvc] = useState(""); const [name, setName] = useState(""); const [email, setEmail] = useState(user?.email || ""); const [remember, setRemember] = useState(false); const [error, setError] = useState(""); const artefactsToBuy = artefact ? [artefact] : cartItems || []; const total = artefactsToBuy.reduce((sum, art) => sum + art.price, 0); function validateEmail(email: string) { return ( email.includes("@") && (email.endsWith(".com") || email.endsWith(".co.uk") || email.endsWith(".org") || email.endsWith(".org.uk")) ); } function validateCardNumber(number: string) { return /^\d{12,19}$/.test(number.replace(/\s/g, "")); // 12-19 digits } function validateCVC(number: string) { return /^\d{3,4}$/.test(number); } function validateExpiry(exp: string) { return /^\d{2}\/\d{2}$/.test(exp); } function handlePay() { setError(""); const paymentEmail = user?.email || email; if (!validateEmail(paymentEmail)) { setError("Please enter a valid email"); return; } if (!validateCardNumber(cardNumber)) { setError("Card number must be 12-19 digits."); return; } if (!validateExpiry(expiry)) { setError("Expiry must be in MM/YY format."); return; } if (!validateCVC(cvc)) { setError("CVC must be 3 or 4 digits."); return; } // remove all artefacts that were bought (works for both cart and single) setHiddenArtefactIds((ids) => [...ids, ...artefactsToBuy.map((a) => a.id)]); // todo create receiving api route // todo handle sending to api route const genOrder = () => "#" + Math.random().toString(36).substring(2, 10).toUpperCase(); setOrderNumber(genOrder()); onClose(); setShowThankYouModal(true); setCart((c) => c.filter((a) => !artefactsToBuy.map((x) => x.id).includes(a.id))); } const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) onClose(); }; return (

Checkout {artefact ? artefact.name : artefactsToBuy.length + " item(s)"} {!artefact && ({artefactsToBuy.map((x) => x.name).join(", ")})}

{ e.preventDefault(); handlePay(); }} > {/* Email autofill */} setEmail(e.target.value)} type="email" required autoFocus disabled={!!user?.email} /> {user?.email && (

Signed in as {user.email}

)} setName(e.target.value)} required /> setCardNumber(e.target.value.replace(/\D/g, ""))} maxLength={19} required inputMode="numeric" pattern="\d*" />
setExpiry(e.target.value.replace(/[^0-9/]/g, ""))} maxLength={5} required inputMode="numeric" /> setCvc(e.target.value.replace(/\D/g, ""))} maxLength={4} required inputMode="numeric" />
{error &&

{error}

}
Total: {currencyTickers[selectedCurrency]} {convertPrice(total, selectedCurrency)}
); } function ThankYouModal({ orderNumber, onClose }: { orderNumber: string; onClose: () => void }) { const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) onClose(); }; return (

Thank you for your purchase!

Your order number is:

{orderNumber}

); } return (
{/* --- Cart Button fixed at top right --- */}

Artefact Shop

Discover extraordinary artefacts and collectibles from major seismic events from around the world - Previously studied by our scientists, now available for purchase.

{currentArtefacts .filter((x) => !hiddenArtefactIds.includes(x.id)) .map((artefact) => ( ))}

{currentPage}

{selectedArtefact && } {showCartModal && } {showPaymentModal && (cartCheckout || artefactToBuy) && ( { setShowPaymentModal(false); setArtefactToBuy(null); setCartCheckout(false); }} /> )} {showThankYouModal && orderNumber && ( setShowThankYouModal(false)} /> )} {!selectedArtefact && !showPaymentModal && !showThankYouModal && !showCartModal && (
)}
); }