From 0a054ac8d9182d2884d79b85ba2dacb98f4e1e7a Mon Sep 17 00:00:00 2001 From: senke Date: Tue, 3 Feb 2026 09:50:51 +0100 Subject: [PATCH] refactor(cart): migrate CheckoutView and MarketplaceView to useCartStore and fix stories --- .../components/views/CheckoutView.stories.tsx | 59 +++++++++++++++++++ .../web/src/components/views/CheckoutView.tsx | 27 +++++---- .../views/MarketplaceView.stories.tsx | 22 +++++++ .../src/components/views/MarketplaceView.tsx | 23 +++----- 4 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 apps/web/src/components/views/CheckoutView.stories.tsx create mode 100644 apps/web/src/components/views/MarketplaceView.stories.tsx diff --git a/apps/web/src/components/views/CheckoutView.stories.tsx b/apps/web/src/components/views/CheckoutView.stories.tsx new file mode 100644 index 000000000..dd085eaa7 --- /dev/null +++ b/apps/web/src/components/views/CheckoutView.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { CheckoutView } from './CheckoutView'; +import { useCartStore } from '../../stores/cartStore'; +import { useEffect } from 'react'; + +const MOCK_ITEMS = [ + { + cartId: 'c1', + product: { + id: 'p1', + title: 'Analog Dreams Vol. 2', + price: 24.99, + coverUrl: 'https://picsum.photos/200', + author: 'Vintage Synths', + }, + quantity: 1, + } +]; + +const meta: Meta = { + title: 'Components/Views/CheckoutView', + component: CheckoutView, + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +// Helper decorator to set store state +const withStoreState = (items: any[]) => (Story: any) => { + useEffect(() => { + useCartStore.setState({ items }); + return () => useCartStore.setState({ items: [] }); + }, [items]); + return ; +}; + +export const Default: Story = { + name: 'Par défaut', + decorators: [withStoreState(MOCK_ITEMS as any)], +}; + +export const Processing: Story = { + name: 'Traitement', + decorators: [withStoreState(MOCK_ITEMS as any)], +}; + +export const Success: Story = { + name: 'Succès', + decorators: [withStoreState(MOCK_ITEMS as any)], +}; diff --git a/apps/web/src/components/views/CheckoutView.tsx b/apps/web/src/components/views/CheckoutView.tsx index 4b28d85b9..0ac8f528a 100644 --- a/apps/web/src/components/views/CheckoutView.tsx +++ b/apps/web/src/components/views/CheckoutView.tsx @@ -1,12 +1,13 @@ +import { logger } from '@/utils/logger'; +import { ArrowLeft, CreditCard, Loader2, Lock } from 'lucide-react'; import React, { useState } from 'react'; -import { Card } from '../ui/card'; -import { Button } from '../ui/button'; -import { Input } from '../ui/input'; -import { useCart } from '../../context/CartContext'; -import { CreditCard, Lock, ArrowLeft, Loader2 } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; import { useToast } from '../../components/feedback/ToastProvider'; import { marketplaceService } from '../../services/marketplaceService'; -import { logger } from '@/utils/logger'; +import { useCartStore } from '../../stores/cartStore'; +import { Button } from '../ui/button'; +import { Card } from '../ui/card'; +import { Input } from '../ui/input'; interface CheckoutViewProps { onBack: () => void; @@ -17,7 +18,13 @@ export const CheckoutView: React.FC = ({ onBack, onComplete, }) => { - const { cart, cartTotal, clearCart } = useCart(); + const { cart, cartTotal, clearCart } = useCartStore( + useShallow((state) => ({ + cart: state.items, + cartTotal: state.getTotal(), + clearCart: state.clearCart, + })) + ); const { addToast } = useToast(); const [loading, setLoading] = useState(false); @@ -47,7 +54,7 @@ export const CheckoutView: React.FC = ({ setLoading(true); try { // Map cart items to API format - const orderItems = cart.map((item) => ({ product_id: item.id })); + const orderItems = cart.map((item) => ({ product_id: item.product.id })); await marketplaceService.createOrder(orderItems); addToast('Purchase successful!', 'success'); @@ -225,14 +232,14 @@ export const CheckoutView: React.FC = ({ >
- {item.title} + {item.product.title}
{item.selectedLicense?.name || 'Standard'} License
- ${(item.selectedLicense?.price || item.price).toFixed(2)} + ${(item.selectedLicense?.price || item.product.price).toFixed(2)}
))} diff --git a/apps/web/src/components/views/MarketplaceView.stories.tsx b/apps/web/src/components/views/MarketplaceView.stories.tsx new file mode 100644 index 000000000..a76757b5a --- /dev/null +++ b/apps/web/src/components/views/MarketplaceView.stories.tsx @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { MarketplaceView } from './MarketplaceView'; + +const meta: Meta = { + title: 'Components/Views/MarketplaceView', + component: MarketplaceView, + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { name: 'Par défaut' }; +export const Loading: Story = { name: 'Chargement' }; diff --git a/apps/web/src/components/views/MarketplaceView.tsx b/apps/web/src/components/views/MarketplaceView.tsx index db6adbc98..36c62f96d 100644 --- a/apps/web/src/components/views/MarketplaceView.tsx +++ b/apps/web/src/components/views/MarketplaceView.tsx @@ -5,7 +5,7 @@ import { SearchInput } from '../ui/input'; import { Loader2, SlidersHorizontal } from 'lucide-react'; import { Product } from '../../types'; import { useToast } from '../../components/feedback/ToastProvider'; -import { useCart } from '../../context/CartContext'; +import { useCartStore } from '../../stores/cartStore'; import { ProductCard } from '../marketplace/ProductCard'; import { ProductDetailView } from '../marketplace/ProductDetailView'; import { marketplaceService } from '../../services/marketplaceService'; @@ -13,7 +13,7 @@ import { logger } from '@/utils/logger'; export const MarketplaceView: React.FC = () => { const { addToast } = useToast(); - const { addToCart } = useCart(); + const addToCart = useCartStore((state) => state.addItem); const [loading, setLoading] = useState(true); const [products, setProducts] = useState([]); @@ -23,7 +23,6 @@ export const MarketplaceView: React.FC = () => { const [activeCategory, setActiveCategory] = useState('All'); const [searchQuery, setSearchQuery] = useState(''); const [filtersOpen, setFiltersOpen] = useState(false); - const [playingPreview, setPlayingPreview] = useState(null); useEffect(() => { loadProducts(); @@ -56,14 +55,7 @@ export const MarketplaceView: React.FC = () => { } }; - const togglePreview = (id: string) => { - if (playingPreview === id) { - setPlayingPreview(null); - } else { - setPlayingPreview(id); - addToast('Previewing audio...', 'info'); - } - }; + // Filter Logic const filteredProducts = products.filter((p) => { @@ -171,13 +163,12 @@ export const MarketplaceView: React.FC = () => { { - addToast('Checkout feature coming soon', 'info'); - // In a real app, this would redirect to checkout - }} + onClick={(p) => setSelectedProduct(p)} + onPreview={() => { }} + isPlayingPreview={false} onAddToCart={(p) => { addToast('Added to cart', 'success'); - addToCart(p, p.license_type || 'personal'); + addToCart(p); }} /> ))}