209 lines
8.1 KiB
TypeScript
209 lines
8.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { FaTimes, FaTrash, FaShoppingCart } from 'react-icons/fa';
|
|
import { useCart } from '@/lib/CartContext';
|
|
import { Album, Purchase } from '@/lib/types';
|
|
import PaymentModal from './PaymentModal';
|
|
import PurchaseSuccessModal from './PurchaseSuccessModal';
|
|
|
|
interface CartSidebarProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
|
|
const { cartItems, removeFromCart, clearCart, getCartTotal } = useCart();
|
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
|
const [albumToPurchase, setAlbumToPurchase] = useState<Album | null>(null);
|
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
|
const [latestPurchase, setLatestPurchase] = useState<Purchase | null>(null);
|
|
const [purchasedAlbums, setPurchasedAlbums] = useState<string[]>([]);
|
|
|
|
const handleCheckout = () => {
|
|
if (cartItems.length === 0) return;
|
|
|
|
// For simplicity, we'll purchase the first item
|
|
// In a real app, you'd handle multiple items differently
|
|
if (cartItems.length > 0) {
|
|
setAlbumToPurchase(cartItems[0].album);
|
|
setShowPaymentModal(true);
|
|
}
|
|
};
|
|
|
|
const handlePurchaseSuccess = (albumId: string, transactionId: string) => {
|
|
const purchase: Purchase = {
|
|
albumId,
|
|
transactionId,
|
|
purchaseDate: new Date(),
|
|
};
|
|
|
|
// Load existing purchases
|
|
const savedPurchases = localStorage.getItem('purchases');
|
|
const existingPurchases = savedPurchases ? JSON.parse(savedPurchases) : [];
|
|
const updatedPurchases = [...existingPurchases, purchase];
|
|
|
|
localStorage.setItem('purchases', JSON.stringify(updatedPurchases));
|
|
setPurchasedAlbums([...purchasedAlbums, albumId]);
|
|
setLatestPurchase(purchase);
|
|
|
|
// Remove from cart
|
|
removeFromCart(albumId);
|
|
|
|
setShowPaymentModal(false);
|
|
setShowSuccessModal(true);
|
|
};
|
|
|
|
const handleCloseSuccessModal = () => {
|
|
setShowSuccessModal(false);
|
|
setAlbumToPurchase(null);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
className="fixed inset-0 bg-paper-dark/80 z-40"
|
|
/>
|
|
|
|
{/* Sidebar */}
|
|
<motion.div
|
|
initial={{ x: '100%' }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: '100%' }}
|
|
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
|
className="fixed right-0 top-0 bottom-0 w-full md:w-96 bg-paper-sand border-l-4 border-paper-dark z-50 flex flex-col cardboard-texture"
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-6 border-b-2 border-paper-dark">
|
|
<div className="flex items-center gap-3">
|
|
<FaShoppingCart className="text-2xl text-paper-dark" />
|
|
<h2 className="text-2xl font-bold text-paper-dark">
|
|
Cart ({cartItems.length})
|
|
</h2>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors"
|
|
aria-label="Close cart"
|
|
>
|
|
<FaTimes className="text-xl text-paper-dark" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Cart Items */}
|
|
<div className="flex-1 overflow-y-auto p-6">
|
|
{cartItems.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center h-full text-center">
|
|
<FaShoppingCart className="text-6xl text-paper-gray mb-4" />
|
|
<p className="text-paper-dark text-lg font-medium">Your cart is empty</p>
|
|
<p className="text-paper-brown text-sm mt-2">Add some albums to get started</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{cartItems.map((item) => (
|
|
<motion.div
|
|
key={item.album.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
className="paper-card-light p-4"
|
|
>
|
|
<div className="flex gap-4">
|
|
{/* Album Cover */}
|
|
<div className="w-20 h-20 bg-paper-brown border-2 border-paper-dark flex items-center justify-center flex-shrink-0">
|
|
<span className="text-xs text-paper-dark/50 font-bold text-center px-2">
|
|
{item.album.title}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Album Info */}
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-paper-dark font-semibold truncate">
|
|
{item.album.title}
|
|
</h3>
|
|
<p className="text-sm text-paper-brown">
|
|
{item.album.year} • {item.album.songs.length} tracks
|
|
</p>
|
|
<p className="text-paper-dark font-bold mt-2">
|
|
${item.album.price}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Remove Button */}
|
|
<button
|
|
onClick={() => removeFromCart(item.album.id)}
|
|
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors self-start"
|
|
aria-label="Remove from cart"
|
|
>
|
|
<FaTrash className="text-paper-dark" />
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
{cartItems.length > 0 && (
|
|
<div className="border-t-2 border-paper-dark p-6 space-y-4">
|
|
{/* Total */}
|
|
<div className="flex items-center justify-between text-xl p-4 paper-card-dark">
|
|
<span className="text-paper-light font-semibold">Total</span>
|
|
<span className="text-paper-sand font-bold">
|
|
${getCartTotal().toFixed(2)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Buttons */}
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={handleCheckout}
|
|
className="w-full py-4 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light transition-all shadow-paper-lg"
|
|
>
|
|
Proceed to Checkout
|
|
</button>
|
|
<button
|
|
onClick={clearCart}
|
|
className="w-full py-3 bg-paper-light hover:bg-paper-sand border-2 border-paper-brown font-semibold text-paper-dark transition-all shadow-paper"
|
|
>
|
|
Clear Cart
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Payment Modal */}
|
|
<PaymentModal
|
|
album={albumToPurchase}
|
|
onClose={() => {
|
|
setShowPaymentModal(false);
|
|
setAlbumToPurchase(null);
|
|
}}
|
|
onSuccess={handlePurchaseSuccess}
|
|
/>
|
|
|
|
{/* Purchase Success Modal */}
|
|
<PurchaseSuccessModal
|
|
show={showSuccessModal}
|
|
album={albumToPurchase}
|
|
purchase={latestPurchase}
|
|
onClose={handleCloseSuccessModal}
|
|
/>
|
|
</>
|
|
);
|
|
}
|