- 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
250 lines
9.2 KiB
TypeScript
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>
|
|
);
|
|
};
|