veza/apps/web/src/components/commerce/WishlistView.tsx

210 lines
6.7 KiB
TypeScript
Raw Normal View History

import { Button } from '../ui/button';
import { Card } from '../ui/card';
import { EmptyState } from '../ui/empty-state';
import { Skeleton } from '../ui/skeleton';
import { useCartStore } from '../../stores/cartStore';
import { Product } from '../../types';
import { Heart, Pause, Play, ShoppingCart, Trash2, Zap } from 'lucide-react';
import React, { useState } from 'react';
import { useToast } from '../../components/feedback/ToastProvider';
// Mock Wishlist Data
2026-01-07 18:39:21 +00:00
const MOCK_WISHLIST: any[] = [
{
id: 'w1',
title: 'Analog Dreams Vol. 2',
type: 'sample_pack',
price: 24.99,
currency: 'USD',
rating: 4.8,
coverUrl: 'https://picsum.photos/id/40/300/300',
author: 'Vintage Synths',
description: 'Warm analog pads and leads.',
features: [],
licenses: [],
},
{
id: 'w2',
title: 'Tech House Essentials',
type: 'preset',
price: 19.99,
currency: 'USD',
rating: 4.5,
coverUrl: 'https://picsum.photos/id/45/300/300',
author: 'Club Ready',
description: 'Floor filling serum presets.',
features: [],
licenses: [],
},
{
id: 'w3',
title: 'Cinematic FX',
type: 'sample_pack',
price: 34.5,
currency: 'USD',
rating: 5.0,
coverUrl: 'https://picsum.photos/id/50/300/300',
author: 'Sound Design Co',
isHot: true,
description: 'Impacts, risers, and drops.',
features: [],
licenses: [],
},
];
export const WishlistView: React.FC = () => {
const addToCart = useCartStore((state) => state.addItem);
const { addToast } = useToast();
const [isLoading] = useState(false);
const [wishlist, setWishlist] = useState<Product[]>(MOCK_WISHLIST);
const [playingPreview, setPlayingPreview] = useState<string | null>(null);
const handleRemove = (id: string) => {
setWishlist((prev) => prev.filter((p) => p.id !== id));
addToast('Removed from wishlist', 'info');
};
const handleAddToCart = (product: Product) => {
addToCart(product);
handleRemove(product.id);
};
const handleAddAll = () => {
wishlist.forEach((p) => addToCart(p));
setWishlist([]);
addToast('All items moved to cart', 'success');
};
if (isLoading) {
return (
<div className="max-w-6xl mx-auto pb-20">
<div className="flex flex-col md:flex-row justify-between items-end border-b border-border/50 pb-6 gap-4 mb-8">
<div>
<Skeleton className="h-9 w-48 mb-2" />
<Skeleton variant="text" className="w-32" />
</div>
<Skeleton className="h-10 w-44" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-96 w-full rounded-lg" />
))}
</div>
</div>
);
}
if (wishlist.length === 0) {
return (
<EmptyState
variant="centered"
icon={<Heart className="w-full h-full" />}
title="Your wishlist is empty"
description="Explore the marketplace and save items you love."
action={{
label: 'Browse Marketplace',
onClick: () => (window.location.href = '/marketplace'),
}}
size="lg"
className="min-h-96"
/>
);
}
return (
<div className="animate-fadeIn max-w-6xl mx-auto pb-20">
<div className="flex flex-col md:flex-row justify-between items-end border-b border-border/50 pb-6 gap-4 mb-8">
<div>
<h1 className="text-3xl font-display font-bold text-white mb-2">
WISHLIST
</h1>
<p className="text-muted-foreground font-mono text-sm">
{wishlist.length} saved items
</p>
</div>
<Button
variant="primary"
icon={<ShoppingCart className="w-4 h-4" />}
onClick={handleAddAll}
>
ADD ALL TO CART
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{wishlist.map((product) => (
<Card
key={product.id}
variant="default"
className="p-4 group hover:border-border/50 transition-all"
>
<div className="flex gap-4">
<div className="relative w-24 h-24 bg-muted rounded-lg overflow-hidden flex-shrink-0">
<img
src={product.coverUrl}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-[var(--duration-slow)]"
/>
<div
className="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
onClick={() =>
setPlayingPreview(
playingPreview === product.id ? null : product.id,
)
}
>
{playingPreview === product.id ? (
<Pause className="w-8 h-8 text-white" />
) : (
<Play className="w-8 h-8 text-white fill-current" />
)}
2026-01-07 18:39:21 +00:00
</div>
{product.isHot && (
<div className="absolute top-1 left-1 bg-warning text-warning-foreground text-xs font-bold px-1.5 py-0.5 rounded">
<Zap className="w-2 h-2 inline" /> HOT
</div>
)}
</div>
<div className="flex-1 min-w-0 flex flex-col justify-between">
2026-01-07 18:39:21 +00:00
<div>
<h3 className="font-bold text-white truncate">
{product.title}
</h3>
<p className="text-xs text-muted-foreground truncate">
{product.author}
</p>
<p className="text-xs text-muted-foreground mt-1 capitalize">
{product.type}
</p>
2026-01-07 18:39:21 +00:00
</div>
<div className="text-lg font-mono font-bold text-muted-foreground">
${product.price}
</div>
</div>
</div>
<div className="flex gap-2 mt-4 pt-4 border-t border-border/30">
<Button
variant="secondary"
size="sm"
className="flex-1"
onClick={() => handleAddToCart(product)}
>
Add to Cart
</Button>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-destructive"
onClick={() => handleRemove(product.id)}
>
<Trash2 className="w-4 h-4" />
</Button>
2026-01-07 18:39:21 +00:00
</div>
</Card>
))}
</div>
</div>
);
};