veza/apps/web/src/components/seller/SellerDashboardView.tsx
senke 6974c12a25 aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 11:50:46 +01:00

250 lines
9.2 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import {
Plus,
TrendingUp,
DollarSign,
Package,
Users,
Eye,
MoreHorizontal,
Zap,
Loader2,
} from 'lucide-react';
import { FlashSaleModal } from './modals/FlashSaleModal';
import { useToast } from '../../context/ToastContext';
import { Product } from '../../types';
import { marketplaceService } from '../../services/marketplaceService';
import { commerceService } from '../../services/commerceService';
import { logger } from '@/utils/logger';
interface SellerDashboardProps {
onCreateProduct: () => void;
}
export const SellerDashboardView: React.FC<SellerDashboardProps> = ({
onCreateProduct,
}) => {
const { addToast } = useToast();
const [showFlashSale, setShowFlashSale] = useState(false);
const [products, setProducts] = useState<Product[]>([]);
const [sales, setSales] = useState<any[]>([]);
const [stats, setStats] = useState<any>({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const [prods, salesData, statsData] = await Promise.all([
marketplaceService.listProducts({ seller_id: 'me' }),
commerceService.getSales(),
commerceService.getSellerStats(),
]);
setProducts(prods.products || []);
setSales(salesData);
setStats(statsData);
} catch (e) {
logger.error('Error loading seller dashboard data', {
error: e instanceof Error ? e.message : String(e),
stack: e instanceof Error ? e.stack : undefined,
});
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading)
return (
<div className="flex justify-center py-24">
<Loader2 className="w-10 h-10 text-kodo-steel animate-spin" />
</div>
);
return (
<div className="animate-fadeIn space-y-8 pb-20">
{/* Header */}
<div className="flex flex-col md:flex-row justify-between items-end gap-4">
<div>
<h2 className="text-2xl font-display font-bold text-white mb-2">
SELLER DASHBOARD
</h2>
<p className="text-kodo-content-dim font-mono text-sm">
Manage your products, sales, and analytics.
</p>
</div>
<div className="flex gap-4">
<Button
variant="gaming"
icon={<Zap className="w-4 h-4" />}
onClick={() => setShowFlashSale(true)}
>
FLASH SALE
</Button>
<Button
variant="primary"
icon={<Plus className="w-4 h-4" />}
onClick={onCreateProduct}
>
CREATE PRODUCT
</Button>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card variant="default" className="p-6 relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<DollarSign className="w-16 h-16 text-kodo-gold" />
</div>
<div className="text-kodo-content-dim text-xs font-bold uppercase mb-1">
Total Revenue
</div>
<div className="text-3xl font-mono font-bold text-white mb-2">
${stats.revenue?.toLocaleString()}
</div>
<div className="text-xs text-kodo-lime flex items-center gap-1">
<TrendingUp className="w-3 h-3" /> +12.5% this month
</div>
</Card>
<Card variant="default" className="p-6 relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Package className="w-16 h-16 text-kodo-steel" />
</div>
<div className="text-kodo-content-dim text-xs font-bold uppercase mb-1">
Total Sales
</div>
<div className="text-3xl font-mono font-bold text-white mb-2">
{stats.sales}
</div>
<div className="text-xs text-kodo-lime flex items-center gap-1">
<TrendingUp className="w-3 h-3" /> +5.0% this month
</div>
</Card>
<Card variant="default" className="p-6 relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Eye className="w-16 h-16 text-kodo-magenta" />
</div>
<div className="text-kodo-content-dim text-xs font-bold uppercase mb-1">
Page Views
</div>
<div className="text-3xl font-mono font-bold text-white mb-2">
{stats.views > 1000
? `${(stats.views / 1000).toFixed(1)}K`
: stats.views}
</div>
<div className="text-xs text-kodo-red flex items-center gap-1">
<TrendingUp className="w-3 h-3 rotate-180" /> -2.4% this month
</div>
</Card>
<Card variant="default" className="p-6 relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Users className="w-16 h-16 text-white" />
</div>
<div className="text-kodo-content-dim text-xs font-bold uppercase mb-1">
Conversion Rate
</div>
<div className="text-3xl font-mono font-bold text-white mb-2">
{stats.conversion}%
</div>
<div className="text-xs text-kodo-lime flex items-center gap-1">
<TrendingUp className="w-3 h-3" /> +0.8% this month
</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Top Products */}
<div className="lg:col-span-2">
<Card variant="default" className="h-full">
<div className="flex justify-between items-center mb-6">
<h3 className="font-bold text-white">Top Products</h3>
<Button variant="ghost" size="sm">
View All
</Button>
</div>
<div className="space-y-4">
{products.map((product, i) => (
<div
key={product.id}
className="flex items-center gap-4 p-4 bg-kodo-ink rounded-lg border border-transparent hover:border-kodo-steel transition-all"
>
<div className="w-8 text-center font-mono text-kodo-content-dim">
{i + 1}
</div>
<img
src={product.coverUrl}
className="w-12 h-12 rounded object-cover"
/>
<div className="flex-1 min-w-0">
<div className="font-bold text-white truncate">
{product.title}
</div>
<div className="text-xs text-kodo-content-dim">
{product.reviewCount} reviews {product.rating} stars
</div>
</div>
<div className="text-right">
<div className="font-bold text-white">${product.price}</div>
<div className="text-xs text-kodo-steel">
{Math.floor(Math.random() * 100)} sales
</div>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="w-4 h-4" />
</Button>
</div>
))}
</div>
</Card>
</div>
{/* Recent Sales */}
<div>
<Card variant="default" className="h-full">
<h3 className="font-bold text-white mb-6">Recent Sales</h3>
<div className="space-y-4 relative">
<div className="absolute left-2.5 top-2 bottom-2 w-px bg-kodo-steel"></div>
{sales.map((sale) => (
<div key={sale.id} className="relative pl-8">
<div className="absolute left-0 top-1.5 w-5 h-5 bg-kodo-graphite border border-kodo-lime rounded-full flex items-center justify-center">
<div className="w-2 h-2 bg-kodo-lime rounded-full"></div>
</div>
<div className="text-sm text-white font-bold">
{sale.product}
</div>
<div className="text-xs text-kodo-content-dim flex justify-between mt-1">
<span>{sale.buyer}</span>
<span>${sale.amount}</span>
</div>
<div className="text-[10px] text-kodo-content-dim mt-1">
{sale.date}
</div>
</div>
))}
</div>
</Card>
</div>
</div>
{showFlashSale && (
<FlashSaleModal
products={products}
onClose={() => setShowFlashSale(false)}
onStart={(config) =>
addToast(
`Flash Sale started for ${config.productIds.length} products!`,
'success',
)
}
/>
)}
</div>
);
};