297 lines
11 KiB
TypeScript
297 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import { motion } from 'framer-motion';
|
|
import { FaPlay, FaShoppingCart, FaArrowLeft, FaClock, FaMusic } from 'react-icons/fa';
|
|
import { Album, Purchase } from '@/lib/types';
|
|
import { useCart } from '@/lib/CartContext';
|
|
import { useAlbums } from '@/lib/AlbumsContext';
|
|
import { formatPrice } from '@/lib/utils';
|
|
import Header from '@/components/Header';
|
|
import CartSidebar from '@/components/CartSidebar';
|
|
import MusicPlayer from '@/components/MusicPlayer';
|
|
import PaymentModal from '@/components/PaymentModal';
|
|
import PurchaseSuccessModal from '@/components/PurchaseSuccessModal';
|
|
|
|
export default function AlbumDetailPage() {
|
|
const params = useParams();
|
|
const router = useRouter();
|
|
const { addToCart, isInCart } = useCart();
|
|
const { albums } = useAlbums();
|
|
const [album, setAlbum] = useState<Album | null>(null);
|
|
const [purchasedAlbums, setPurchasedAlbums] = useState<string[]>([]);
|
|
const [purchases, setPurchases] = useState<Purchase[]>([]);
|
|
const [cartOpen, setCartOpen] = useState(false);
|
|
const [currentAlbum, setCurrentAlbum] = useState<Album | null>(null);
|
|
const [albumToPurchase, setAlbumToPurchase] = useState<Album | null>(null);
|
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
|
const [latestPurchase, setLatestPurchase] = useState<Purchase | null>(null);
|
|
|
|
useEffect(() => {
|
|
const foundAlbum = albums.find((a) => a.id === params.id);
|
|
setAlbum(foundAlbum || null);
|
|
|
|
// Load purchases
|
|
const savedPurchases = localStorage.getItem('purchases');
|
|
if (savedPurchases) {
|
|
const parsedPurchases = JSON.parse(savedPurchases);
|
|
setPurchases(parsedPurchases);
|
|
setPurchasedAlbums(parsedPurchases.map((p: Purchase) => p.albumId));
|
|
}
|
|
}, [params.id]);
|
|
|
|
const handlePurchaseSuccess = (albumId: string, transactionId: string) => {
|
|
const purchase: Purchase = {
|
|
albumId,
|
|
transactionId,
|
|
purchaseDate: new Date(),
|
|
};
|
|
|
|
const updatedPurchases = [...purchases, purchase];
|
|
setPurchases(updatedPurchases);
|
|
setPurchasedAlbums([...purchasedAlbums, albumId]);
|
|
setLatestPurchase(purchase);
|
|
|
|
localStorage.setItem('purchases', JSON.stringify(updatedPurchases));
|
|
|
|
setAlbumToPurchase(null);
|
|
setShowSuccessModal(true);
|
|
};
|
|
|
|
const handleCloseSuccessModal = () => {
|
|
setShowSuccessModal(false);
|
|
if (albumToPurchase) {
|
|
setCurrentAlbum(albumToPurchase);
|
|
}
|
|
};
|
|
|
|
const handleAddToCart = () => {
|
|
if (album && !isInCart(album.id)) {
|
|
addToCart(album);
|
|
}
|
|
};
|
|
|
|
const isPurchased = album ? purchasedAlbums.includes(album.id) : false;
|
|
const inCart = album ? isInCart(album.id) : false;
|
|
|
|
if (!album) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h1 className="text-3xl font-bold text-paper-dark mb-4">Album not found</h1>
|
|
<button
|
|
onClick={() => router.push('/')}
|
|
className="px-6 py-3 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark text-paper-light transition-all shadow-paper"
|
|
>
|
|
Go Back Home
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const totalDuration = album.songs.reduce((total, song) => {
|
|
const [minutes, seconds] = song.duration.split(':').map(Number);
|
|
return total + minutes * 60 + seconds;
|
|
}, 0);
|
|
|
|
const formatTotalDuration = (seconds: number) => {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const mins = Math.floor((seconds % 3600) / 60);
|
|
if (hours > 0) {
|
|
return `${hours}h ${mins}m`;
|
|
}
|
|
return `${mins} min`;
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Header onCartClick={() => setCartOpen(true)} />
|
|
<div className="min-h-screen pt-20">
|
|
{/* Back Button */}
|
|
<div className="max-w-7xl mx-auto px-4 md:px-8 py-6">
|
|
<button
|
|
onClick={() => router.push('/')}
|
|
className="flex items-center gap-2 text-paper-dark hover:text-paper-brown transition-colors font-medium border-b-2 border-transparent hover:border-paper-brown"
|
|
>
|
|
<FaArrowLeft />
|
|
Back to Albums
|
|
</button>
|
|
</div>
|
|
|
|
{/* Album Header */}
|
|
<div className="max-w-7xl mx-auto px-4 md:px-8 pb-12">
|
|
<div className="grid md:grid-cols-2 gap-12 items-start">
|
|
{/* Album Cover */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
className="relative"
|
|
>
|
|
<div className="aspect-square overflow-hidden bg-paper-brown flex items-center justify-center border-4 border-paper-dark shadow-paper-lg">
|
|
<div className="absolute inset-0 cardboard-texture"></div>
|
|
<div className="relative z-10 text-6xl md:text-8xl font-bold text-paper-dark/20 p-8 text-center">
|
|
{album.title}
|
|
</div>
|
|
</div>
|
|
|
|
{isPurchased && (
|
|
<div className="absolute top-6 right-6 bg-paper-dark text-paper-light px-4 py-2 border-2 border-paper-brown text-sm font-semibold shadow-paper">
|
|
Owned
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
|
|
{/* Album Info */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
className="space-y-6"
|
|
>
|
|
<div>
|
|
<p className="text-sm text-paper-brown mb-2 font-medium">{album.year} • {album.genre}</p>
|
|
<h1 className="text-4xl md:text-5xl font-bold text-paper-dark mb-4 tracking-tight border-b-4 border-paper-dark inline-block pb-2">
|
|
{album.title}
|
|
</h1>
|
|
<p className="text-xl text-paper-dark leading-relaxed mt-6">
|
|
{album.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Album Stats */}
|
|
<div className="flex gap-6 text-paper-dark p-4 paper-card">
|
|
<div className="flex items-center gap-2">
|
|
<FaMusic className="text-paper-brown" />
|
|
<span className="font-medium">{album.songs.length} tracks</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<FaClock className="text-paper-brown" />
|
|
<span className="font-medium">{formatTotalDuration(totalDuration)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Price */}
|
|
{!isPurchased && (
|
|
<div className="text-3xl font-bold text-paper-light p-4 paper-card-dark">
|
|
{formatPrice(album.price)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex gap-4 pt-4">
|
|
<button
|
|
onClick={() => setCurrentAlbum(album)}
|
|
className="flex-1 py-4 bg-paper-sand hover:bg-paper-gray border-2 border-paper-dark font-semibold text-paper-dark transition-all shadow-paper hover:shadow-paper-lg flex items-center justify-center gap-2"
|
|
>
|
|
<FaPlay />
|
|
{isPurchased ? 'Play Album' : 'Preview'}
|
|
</button>
|
|
|
|
{!isPurchased && (
|
|
<>
|
|
<button
|
|
onClick={() => setAlbumToPurchase(album)}
|
|
className="flex-1 py-4 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light transition-all shadow-paper-lg flex items-center justify-center gap-2"
|
|
>
|
|
Buy Now
|
|
</button>
|
|
<button
|
|
onClick={handleAddToCart}
|
|
disabled={inCart}
|
|
className={`py-4 px-6 ${
|
|
inCart
|
|
? 'bg-paper-gray cursor-not-allowed opacity-50'
|
|
: 'bg-paper-light hover:bg-paper-sand border-2 border-paper-brown'
|
|
} font-semibold text-paper-dark transition-all flex items-center justify-center gap-2 shadow-paper`}
|
|
>
|
|
<FaShoppingCart />
|
|
{inCart ? 'In Cart' : 'Add to Cart'}
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Track List */}
|
|
<div className="max-w-7xl mx-auto px-4 md:px-8 pb-20">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
|
className="paper-card p-8 cardboard-texture"
|
|
>
|
|
<h2 className="text-2xl font-bold text-paper-dark mb-6 border-b-2 border-paper-dark pb-2">Track List</h2>
|
|
<div className="space-y-2">
|
|
{album.songs.map((song, index) => (
|
|
<motion.div
|
|
key={song.id}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.4, delay: index * 0.05 }}
|
|
className="flex items-center justify-between p-4 hover:bg-paper-sand border-2 border-transparent hover:border-paper-brown transition-all group cursor-pointer"
|
|
onClick={() => setCurrentAlbum(album)}
|
|
>
|
|
<div className="flex items-center gap-4 flex-1">
|
|
<span className="text-paper-gray font-mono text-sm w-8">
|
|
{String(index + 1).padStart(2, '0')}
|
|
</span>
|
|
<div className="flex-1">
|
|
<h3 className="text-paper-dark font-medium group-hover:font-bold transition-all">
|
|
{song.title}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-6">
|
|
{!isPurchased && (
|
|
<span className="text-xs text-paper-dark px-2 py-1 bg-paper-brown/20 border border-paper-brown">
|
|
Preview
|
|
</span>
|
|
)}
|
|
<span className="text-paper-brown text-sm font-mono">
|
|
{song.duration}
|
|
</span>
|
|
<FaPlay className="text-paper-dark opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Music Player */}
|
|
{currentAlbum && (
|
|
<MusicPlayer
|
|
album={currentAlbum}
|
|
isPurchased={purchasedAlbums.includes(currentAlbum.id)}
|
|
onClose={() => setCurrentAlbum(null)}
|
|
onPurchase={setAlbumToPurchase}
|
|
/>
|
|
)}
|
|
|
|
{/* Payment Modal */}
|
|
<PaymentModal
|
|
album={albumToPurchase}
|
|
onClose={() => setAlbumToPurchase(null)}
|
|
onSuccess={handlePurchaseSuccess}
|
|
/>
|
|
|
|
{/* Purchase Success Modal */}
|
|
<PurchaseSuccessModal
|
|
show={showSuccessModal}
|
|
album={albumToPurchase}
|
|
purchase={latestPurchase}
|
|
onClose={handleCloseSuccessModal}
|
|
/>
|
|
|
|
{/* Cart Sidebar */}
|
|
<CartSidebar isOpen={cartOpen} onClose={() => setCartOpen(false)} />
|
|
</>
|
|
);
|
|
}
|