refactor(cart): migrate CheckoutView and MarketplaceView to useCartStore and fix stories
This commit is contained in:
parent
5e8e8b5875
commit
0a054ac8d9
4 changed files with 105 additions and 26 deletions
59
apps/web/src/components/views/CheckoutView.stories.tsx
Normal file
59
apps/web/src/components/views/CheckoutView.stories.tsx
Normal file
|
|
@ -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<typeof CheckoutView> = {
|
||||
title: 'Components/Views/CheckoutView',
|
||||
component: CheckoutView,
|
||||
parameters: { layout: 'fullscreen' },
|
||||
tags: ['autodocs'],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Helper decorator to set store state
|
||||
const withStoreState = (items: any[]) => (Story: any) => {
|
||||
useEffect(() => {
|
||||
useCartStore.setState({ items });
|
||||
return () => useCartStore.setState({ items: [] });
|
||||
}, [items]);
|
||||
return <Story />;
|
||||
};
|
||||
|
||||
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)],
|
||||
};
|
||||
|
|
@ -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<CheckoutViewProps> = ({
|
|||
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<CheckoutViewProps> = ({
|
|||
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<CheckoutViewProps> = ({
|
|||
>
|
||||
<div className="flex-1 pr-4">
|
||||
<div className="text-kodo-text-main font-medium truncate">
|
||||
{item.title}
|
||||
{item.product.title}
|
||||
</div>
|
||||
<div className="text-xs text-kodo-content-dim">
|
||||
{item.selectedLicense?.name || 'Standard'} License
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-white font-mono">
|
||||
${(item.selectedLicense?.price || item.price).toFixed(2)}
|
||||
${(item.selectedLicense?.price || item.product.price).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
22
apps/web/src/components/views/MarketplaceView.stories.tsx
Normal file
22
apps/web/src/components/views/MarketplaceView.stories.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { MarketplaceView } from './MarketplaceView';
|
||||
|
||||
const meta: Meta<typeof MarketplaceView> = {
|
||||
title: 'Components/Views/MarketplaceView',
|
||||
component: MarketplaceView,
|
||||
parameters: { layout: 'fullscreen' },
|
||||
tags: ['autodocs'],
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="bg-kodo-background min-h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = { name: 'Par défaut' };
|
||||
export const Loading: Story = { name: 'Chargement' };
|
||||
|
|
@ -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<Product[]>([]);
|
||||
|
|
@ -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<string | null>(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 = () => {
|
|||
<ProductCard
|
||||
key={product.id}
|
||||
product={product}
|
||||
onPurchase={(p) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in a new issue