feat(seller): add transfers history card to SellerDashboard
This commit is contained in:
parent
b3c74428d8
commit
fdd750d772
3 changed files with 76 additions and 2 deletions
|
|
@ -25,13 +25,14 @@ import {
|
|||
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 } from '../../services/commerceService';
|
||||
import { commerceService, type SellerTransfer } from '../../services/commerceService';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
interface SellerDashboardProps {
|
||||
|
|
@ -54,6 +55,7 @@ export const SellerDashboardView: React.FC<SellerDashboardProps> = ({
|
|||
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[]>([]);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -82,6 +84,13 @@ export const SellerDashboardView: React.FC<SellerDashboardProps> = ({
|
|||
setStripeConnectAvailable(status === 503 ? false : true);
|
||||
setBalance(null);
|
||||
}
|
||||
// v0.603: Fetch transfer history
|
||||
try {
|
||||
const transfersData = await commerceService.getSellerTransfers();
|
||||
setTransfers(transfersData);
|
||||
} catch {
|
||||
setTransfers([]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Error loading seller dashboard data', {
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
|
|
@ -235,6 +244,54 @@ export const SellerDashboardView: React.FC<SellerDashboardProps> = ({
|
|||
</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>
|
||||
)}
|
||||
|
||||
<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" />
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ interface TopProductStats {
|
|||
sales_count: number;
|
||||
}
|
||||
|
||||
export interface SellerTransfer {
|
||||
id: string;
|
||||
order_id: string;
|
||||
amount_cents: number;
|
||||
platform_fee_cents: number;
|
||||
currency: string;
|
||||
status: 'pending' | 'completed' | 'failed' | 'skipped';
|
||||
error_message?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
function formatRelativeDate(dateStr: string): string {
|
||||
const d = new Date(dateStr);
|
||||
const now = new Date();
|
||||
|
|
@ -178,6 +189,12 @@ export const commerceService = {
|
|||
};
|
||||
},
|
||||
|
||||
// v0.603: Seller transfer history
|
||||
getSellerTransfers: async (): Promise<SellerTransfer[]> => {
|
||||
const res = await apiClient.get<{ data?: SellerTransfer[] }>('/sell/transfers');
|
||||
return (res.data?.data ?? []) as SellerTransfer[];
|
||||
},
|
||||
|
||||
// v0.602 P3: Stripe Connect onboarding
|
||||
connectStripeOnboard: async (): Promise<{ onboarding_url: string }> => {
|
||||
const res = await apiClient.post<{ data?: { onboarding_url?: string } }>('/sell/connect/onboard', {});
|
||||
|
|
|
|||
|
|
@ -144,5 +144,5 @@ func (h *SellHandler) GetSellerTransfers(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
RespondSuccess(c, http.StatusOK, gin.H{"data": transfers})
|
||||
RespondSuccess(c, http.StatusOK, transfers)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue