- Fix 98 TypeScript errors across 37 files: - Service layer double-unwrapping (subscriptionService, distributionService, gearService) - Self-referencing variables in SearchPageResults - FeedView/ExploreView .posts→.items alignment - useQueueSync Zustand subscribe API - AdminAuditLogsView missing interface fields - Toast proxy type, interceptor type narrowing - 22 unused imports/variables removed - 5 storybook mock data fixes - Align frontend API calls with backend endpoints: - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics) - Chat: chatService uses /conversations (was mock data), WS URL from backend token - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros) - Settings: suppress 2FA toast error when endpoint unavailable - Fix marketplace products: seed uses 'active' status (was 'published') - Enrich seed: admin follows all creators (feed has content) - Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%) Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc. - Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
563 lines
22 KiB
TypeScript
563 lines
22 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Card } from '../ui/card';
|
|
import { Button } from '../ui/button';
|
|
import { EmptyState } from '../ui/empty-state';
|
|
import { ErrorDisplay } from '@/components/ui/ErrorDisplay';
|
|
import {
|
|
LineChart,
|
|
Line,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
} from 'recharts';
|
|
import {
|
|
Plus,
|
|
TrendingUp,
|
|
DollarSign,
|
|
Package,
|
|
Users,
|
|
Eye,
|
|
MoreHorizontal,
|
|
Zap,
|
|
Loader2,
|
|
RefreshCcw,
|
|
Wallet,
|
|
CreditCard,
|
|
ArrowDownLeft,
|
|
} from 'lucide-react';
|
|
import { FlashSaleModal } from './modals/FlashSaleModal';
|
|
import { RefundRequestModal } from '../commerce/modals/RefundRequestModal';
|
|
import { useToast } from '../../components/feedback/ToastProvider';
|
|
import { Product } from '../../types';
|
|
import { marketplaceService } from '../../services/marketplaceService';
|
|
import { commerceService, type SellerTransfer } 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 [evolution, setEvolution] = useState<{ date: string; revenue: number; sales_count: number }[]>([]);
|
|
const [topProducts, setTopProducts] = useState<{ product_id: string; title: string; revenue: number; sales_count: number }[]>([]);
|
|
const [chartPeriod, setChartPeriod] = useState<'day' | 'week' | 'month'>('week');
|
|
const [refundOrderId, setRefundOrderId] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const [balance, setBalance] = useState<{ connected: boolean; available: number; pending: number } | null>(null);
|
|
const [stripeConnectAvailable, setStripeConnectAvailable] = useState<boolean | null>(null);
|
|
const [transfers, setTransfers] = useState<SellerTransfer[]>([]);
|
|
// v0.12.0 F254: Marketplace balance
|
|
const [marketplaceBalance, setMarketplaceBalance] = useState<{
|
|
available_cents: number;
|
|
pending_cents: number;
|
|
total_earned_cents: number;
|
|
total_paid_out_cents: number;
|
|
currency: string;
|
|
} | null>(null);
|
|
const [payoutLoading, setPayoutLoading] = useState(false);
|
|
|
|
const fetchData = useCallback(async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const [prods, salesData, statsData, evolutionData, topData] = await Promise.all([
|
|
marketplaceService.listProducts({ seller_id: 'me' }).catch(() => ({ products: [] })),
|
|
commerceService.getSales().catch(() => []),
|
|
commerceService.getSellerStats().catch(() => ({})),
|
|
commerceService.getSellerStatsEvolution(chartPeriod).catch(() => []),
|
|
commerceService.getSellerTopProducts(10).catch(() => []),
|
|
]);
|
|
setProducts(prods.products || []);
|
|
setSales(salesData || []);
|
|
setStats(statsData || {});
|
|
setEvolution(evolutionData || []);
|
|
setTopProducts(topData || []);
|
|
|
|
// v0.602 P3: Fetch balance separately (may 503 if Stripe Connect not configured)
|
|
try {
|
|
const balanceData = await commerceService.getSellerBalance();
|
|
setBalance(balanceData);
|
|
setStripeConnectAvailable(true);
|
|
} catch (balErr: unknown) {
|
|
const status = (balErr as { response?: { status?: number } })?.response?.status;
|
|
setStripeConnectAvailable(status === 503 ? false : true);
|
|
setBalance(null);
|
|
}
|
|
// v0.603: Fetch transfer history
|
|
try {
|
|
const transfersData = await commerceService.getSellerTransfers();
|
|
setTransfers(transfersData);
|
|
} catch {
|
|
setTransfers([]);
|
|
}
|
|
// v0.12.0 F254: Fetch marketplace balance
|
|
try {
|
|
const mktBalance = await marketplaceService.getSellerBalance();
|
|
setMarketplaceBalance(mktBalance);
|
|
} catch {
|
|
setMarketplaceBalance(null);
|
|
}
|
|
} catch (e) {
|
|
logger.error('Error loading seller dashboard data', {
|
|
error: e instanceof Error ? e.message : String(e),
|
|
stack: e instanceof Error ? e.stack : undefined,
|
|
});
|
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [chartPeriod]);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
const handleRequestPayout = useCallback(async () => {
|
|
setPayoutLoading(true);
|
|
try {
|
|
await marketplaceService.requestPayout();
|
|
addToast('Payout requested successfully', 'success');
|
|
fetchData();
|
|
} catch (err: unknown) {
|
|
const msg = (err as { response?: { data?: { error?: { message?: string } } } })?.response?.data?.error?.message || 'Failed to request payout';
|
|
addToast(msg, 'error');
|
|
} finally {
|
|
setPayoutLoading(false);
|
|
}
|
|
}, [addToast, fetchData]);
|
|
|
|
const handleConnectPayments = useCallback(async () => {
|
|
try {
|
|
const { onboarding_url } = await commerceService.connectStripeOnboard();
|
|
if (onboarding_url) {
|
|
window.location.href = onboarding_url;
|
|
} else {
|
|
addToast('Could not get onboarding link', 'error');
|
|
}
|
|
} catch (err) {
|
|
logger.error('Connect Stripe onboard failed', { error: err });
|
|
addToast('Failed to start payment setup', 'error');
|
|
}
|
|
}, [addToast]);
|
|
|
|
if (loading)
|
|
return (
|
|
<div className="flex justify-center py-24">
|
|
<Loader2 className="w-10 h-10 text-muted-foreground animate-spin" />
|
|
</div>
|
|
);
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="pb-20">
|
|
<ErrorDisplay
|
|
error={error}
|
|
onRetry={fetchData}
|
|
title="Failed to load seller dashboard"
|
|
context={{ action: 'loading', resource: 'seller dashboard' }}
|
|
variant="card"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (products.length === 0 && sales.length === 0) {
|
|
return (
|
|
<div className="animate-fadeIn space-y-8 pb-20">
|
|
<div className="flex flex-col md:flex-row justify-between items-end gap-4">
|
|
<div>
|
|
<h2 className="text-2xl font-heading font-bold text-foreground mb-2">
|
|
SELLER DASHBOARD
|
|
</h2>
|
|
<p className="text-muted-foreground font-mono text-sm">
|
|
Manage your products, sales, and analytics.
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant="primary"
|
|
icon={<Plus className="w-4 h-4" />}
|
|
onClick={onCreateProduct}
|
|
>
|
|
CREATE PRODUCT
|
|
</Button>
|
|
</div>
|
|
<EmptyState
|
|
icon={<Package className="w-full h-full" />}
|
|
title="No products yet"
|
|
description="Create your first product to start selling and see your dashboard stats."
|
|
action={{
|
|
label: 'Create Product',
|
|
onClick: onCreateProduct,
|
|
}}
|
|
size="lg"
|
|
className="min-h-96"
|
|
/>
|
|
</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-heading font-bold text-foreground mb-2">
|
|
SELLER DASHBOARD
|
|
</h2>
|
|
<p className="text-muted-foreground font-mono text-sm">
|
|
Manage your products, sales, and analytics.
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<Button
|
|
variant="glass"
|
|
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">
|
|
{/* v0.602 P3: Stripe Connect balance card */}
|
|
{stripeConnectAvailable !== false && (
|
|
<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">
|
|
<Wallet className="w-16 h-16 text-primary" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Payout Balance
|
|
</div>
|
|
{balance?.connected ? (
|
|
<>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-1">
|
|
€{(balance.available + balance.pending).toLocaleString(undefined, { minimumFractionDigits: 2 })}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
Available: €{balance.available.toFixed(2)} · Pending: €{balance.pending.toFixed(2)}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="text-sm text-muted-foreground mb-3">
|
|
Configure Stripe to receive payouts
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
icon={<CreditCard className="w-4 h-4" />}
|
|
onClick={handleConnectPayments}
|
|
>
|
|
Configurer les paiements
|
|
</Button>
|
|
</>
|
|
)}
|
|
</Card>
|
|
)}
|
|
|
|
{stripeConnectAvailable !== false && (
|
|
<Card variant="default" className="p-6 relative overflow-hidden group lg:col-span-2">
|
|
<div className="absolute right-0 top-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
|
|
<ArrowDownLeft className="w-16 h-16 text-primary" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-3">
|
|
Transfer History
|
|
</div>
|
|
{transfers.length === 0 ? (
|
|
<div className="text-sm text-muted-foreground">No transfers yet</div>
|
|
) : (
|
|
<div className="space-y-3 max-h-48 overflow-y-auto">
|
|
{transfers.slice(0, 8).map((t) => (
|
|
<div
|
|
key={t.id}
|
|
className="flex items-center justify-between text-sm py-2 border-b border-border last:border-0"
|
|
>
|
|
<div>
|
|
<span className="font-mono text-foreground">
|
|
€{((t.amount_cents + t.platform_fee_cents) / 100).toFixed(2)}
|
|
</span>
|
|
<span className="text-muted-foreground ml-2">
|
|
(fee: €{(t.platform_fee_cents / 100).toFixed(2)})
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className={`text-xs px-2 py-0.5 rounded ${
|
|
t.status === 'completed'
|
|
? 'bg-success/20 text-success'
|
|
: t.status === 'failed'
|
|
? 'bg-destructive/20 text-destructive'
|
|
: 'bg-muted text-muted-foreground'
|
|
}`}
|
|
>
|
|
{t.status}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{new Date(t.created_at).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
)}
|
|
|
|
{/* v0.12.0 F254: Marketplace Balance Card */}
|
|
{marketplaceBalance && (
|
|
<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-success" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Marketplace Balance
|
|
</div>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-1">
|
|
€{(marketplaceBalance.available_cents / 100).toFixed(2)}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mb-3">
|
|
Pending: €{(marketplaceBalance.pending_cents / 100).toFixed(2)} · Total earned: €{(marketplaceBalance.total_earned_cents / 100).toFixed(2)}
|
|
</div>
|
|
{marketplaceBalance.available_cents >= 10000 && (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleRequestPayout}
|
|
disabled={payoutLoading}
|
|
>
|
|
{payoutLoading ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : null}
|
|
Request Payout
|
|
</Button>
|
|
)}
|
|
{marketplaceBalance.available_cents > 0 && marketplaceBalance.available_cents < 10000 && (
|
|
<div className="text-xs text-muted-foreground">
|
|
Min. €100.00 for manual payout · Auto-payout at €50.00 weekly
|
|
</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">
|
|
<DollarSign className="w-16 h-16 text-warning" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Total Revenue
|
|
</div>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-2">
|
|
€{stats.revenue?.toLocaleString() ?? '0'}
|
|
</div>
|
|
<div className="text-xs text-success 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-muted-foreground" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Total Sales
|
|
</div>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-2">
|
|
{stats.sales}
|
|
</div>
|
|
<div className="text-xs text-success 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-destructive" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Page Views
|
|
</div>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-2">
|
|
{stats.views > 1000
|
|
? `${(stats.views / 1000).toFixed(1)}K`
|
|
: stats.views}
|
|
</div>
|
|
<div className="text-xs text-destructive 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-foreground" />
|
|
</div>
|
|
<div className="text-muted-foreground text-xs font-bold uppercase mb-1">
|
|
Conversion Rate
|
|
</div>
|
|
<div className="text-3xl font-mono font-bold text-foreground mb-2">
|
|
{stats.conversion != null ? `${stats.conversion}%` : 'N/A'}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{stats.conversion != null ? 'Views → purchases' : 'Tracking not available'}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Sales Evolution Chart (v0.401 M3) */}
|
|
{evolution.length > 0 && (
|
|
<Card variant="default" className="p-6">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h3 className="font-bold text-foreground">Sales Evolution</h3>
|
|
<div className="flex gap-2">
|
|
{(['day', 'week', 'month'] as const).map((p) => (
|
|
<Button
|
|
key={p}
|
|
variant={chartPeriod === p ? 'default' : 'outline'}
|
|
size="sm"
|
|
onClick={() => setChartPeriod(p)}
|
|
>
|
|
{p.charAt(0).toUpperCase() + p.slice(1)}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="h-64">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={evolution}>
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
|
<XAxis dataKey="date" className="text-xs" tick={{ fill: 'currentColor' }} />
|
|
<YAxis className="text-xs" tick={{ fill: 'currentColor' }} />
|
|
<Tooltip contentStyle={{ backgroundColor: 'hsl(var(--card))', border: '1px solid hsl(var(--border))' }} />
|
|
<Line type="monotone" dataKey="revenue" stroke="hsl(var(--primary))" strokeWidth={2} name="Revenue (€)" />
|
|
<Line type="monotone" dataKey="sales_count" stroke="hsl(var(--muted-foreground))" strokeWidth={2} name="Sales" />
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</Card>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Top Products (v0.401 M3: real data from API) */}
|
|
<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-foreground">Top Products</h3>
|
|
<Button variant="ghost" size="sm">
|
|
View All
|
|
</Button>
|
|
</div>
|
|
<div className="space-y-4">
|
|
{(topProducts.length > 0 ? topProducts : products.slice(0, 5).map((p) => ({ product_id: p.id, title: p.title, revenue: 0, sales_count: 0 }))).map((item, i) => {
|
|
const prod = products.find((p) => p.id === item.product_id);
|
|
return (
|
|
<div
|
|
key={item.product_id}
|
|
className="flex items-center gap-4 p-4 bg-card rounded-lg border border-transparent hover:border-border transition-all"
|
|
>
|
|
<div className="w-8 text-center font-mono text-muted-foreground">
|
|
{i + 1}
|
|
</div>
|
|
<img
|
|
src={prod?.coverUrl ?? prod?.cover_url ?? ''}
|
|
alt=""
|
|
className="w-12 h-12 rounded object-cover bg-muted"
|
|
/>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="font-bold text-foreground truncate">
|
|
{item.title}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{item.sales_count} sales
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="font-bold text-foreground">€{item.revenue.toFixed(2)}</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
revenue
|
|
</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-foreground mb-6">Recent Sales</h3>
|
|
<div className="space-y-4 relative">
|
|
<div className="absolute left-2.5 top-2 bottom-2 w-px bg-muted"></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-muted border border-success rounded-full flex items-center justify-center">
|
|
<div className="w-2 h-2 bg-success rounded-full"></div>
|
|
</div>
|
|
<div className="text-sm text-foreground font-bold">
|
|
{sale.product}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground flex justify-between mt-1">
|
|
<span>{sale.buyer}</span>
|
|
<span>€{typeof sale.amount === 'number' ? sale.amount.toFixed(2) : sale.amount}</span>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground mt-1 flex items-center justify-between">
|
|
<span>{sale.date}</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 text-xs text-muted-foreground hover:text-warning"
|
|
onClick={() => setRefundOrderId(sale.id)}
|
|
>
|
|
<RefreshCcw className="w-3 h-3" /> Refund
|
|
</Button>
|
|
</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',
|
|
)
|
|
}
|
|
/>
|
|
)}
|
|
|
|
{refundOrderId && (
|
|
<RefundRequestModal
|
|
orderId={refundOrderId}
|
|
onClose={() => setRefundOrderId(null)}
|
|
onSuccess={fetchData}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|