SkateSpots/src/components/SellerDashboard.tsx
SkateSpots efa8b2e521 feat(marketplace): 1-10 deck ratings + seller dashboard
- Reviews: confirming a trade now lets the reviewer set a 1-10 skateboard-deck
  rating, stored as ["rating","n"] on the kind-31871 attestation (NIP.md updated).
  The Reputation tab shows each review's decks plus an average.
- New DeckRating component (self-drawn deck glyph) and TradeConfirmRow (rating
  picker + confirm), used for both seller-confirm and buyer-confirm in My Trades.
- New 'Selling' tab: your marketplace listings (active/sold) with one-tap
  Mark sold.
2026-09-27 16:26:24 +07:00

129 lines
5 KiB
TypeScript

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Package, Plus, ExternalLink } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { MarkSoldDialog } from '@/components/MarkSoldDialog';
import { useMarketplaceItems } from '@/hooks/useMarketplace';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import type { MarketplaceItem } from '@/lib/types';
function MyListingRow({ item }: { item: MarketplaceItem }) {
const [openSold, setOpenSold] = useState(false);
const listing = { pubkey: item.pubkey, identifier: item.identifier };
const soldOut = item.status === 'sold';
const price = item.price
? `${Number(item.price.amount).toLocaleString()} ${item.price.currency}`
: item.priceType === 'trade'
? 'Trade'
: 'Free';
return (
<>
<Card className="border-white/8 bg-white/[0.03]">
<CardContent className="flex items-center gap-3 p-3">
{item.images[0] ? (
<img src={item.images[0]} alt="" className="h-12 w-12 shrink-0 rounded-lg object-cover" />
) : (
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-white/10">
<Package className="h-5 w-5 text-gray-500" />
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-bold text-white">{item.title}</p>
<p className="truncate text-xs text-gray-400">
{price}
{item.location ? ` · ${item.location}` : ''}
</p>
</div>
<Badge
variant="outline"
className={soldOut ? 'border-red-500/40 text-red-400' : 'border-emerald-500/40 text-emerald-400'}
>
{soldOut ? 'Sold' : 'Active'}
</Badge>
<Button asChild size="sm" variant="outline" className="gap-1 border-white/15" title="View listing">
<Link to={`/listing/${item.pubkey}/${item.identifier}`}>
<ExternalLink className="h-3.5 w-3.5" />
</Link>
</Button>
{!soldOut && (
<Button
size="sm"
onClick={() => setOpenSold(true)}
className="shrink-0 bg-[#ccff00] font-bold text-black hover:bg-[#ccff00]/90"
>
Mark sold
</Button>
)}
</CardContent>
</Card>
<MarkSoldDialog open={openSold} onOpenChange={setOpenSold} event={item.event} listing={listing} />
</>
);
}
/**
* Marketplace → Selling tab: the seller's own listings, with a one-tap
* "mark sold" (which also records the trade for reputation).
*/
export function SellerDashboard() {
const { user } = useCurrentUser();
const { data: items, isLoading } = useMarketplaceItems(200);
const mine = (items ?? []).filter((i) => i.pubkey === user?.pubkey);
const active = mine.filter((i) => i.status !== 'sold');
const sold = mine.filter((i) => i.status === 'sold');
return (
<div className="space-y-6">
<div className="flex items-center justify-between gap-3">
<h3 className="flex items-center gap-2 text-sm font-black uppercase tracking-wide text-white">
<Package className="h-4 w-4 text-[#ccff00]" />
Your listings
{mine.length > 0 && (
<span className="rounded-full bg-[#ccff00]/15 px-2 py-0.5 text-[10px] font-bold text-[#ccff00]">
{active.length} active
</span>
)}
</h3>
<Button asChild size="sm" className="gap-1.5 bg-orange-500 font-bold text-white hover:bg-orange-600">
<Link to="/create-listing?returnTo=/marketplace">
<Plus className="h-3.5 w-3.5" /> List an item
</Link>
</Button>
</div>
{isLoading ? (
<p className="text-sm text-gray-500">Loading your listings…</p>
) : mine.length === 0 ? (
<Card className="border-dashed border-white/10">
<CardContent className="space-y-3 py-10 text-center">
<Package className="mx-auto h-8 w-8 text-gray-600" />
<p className="text-sm text-gray-400">You have no listings yet.</p>
<Button asChild className="gap-1.5 bg-orange-500 font-bold text-white hover:bg-orange-600">
<Link to="/create-listing?returnTo=/marketplace">
<Plus className="h-3.5 w-3.5" /> List your first item
</Link>
</Button>
</CardContent>
</Card>
) : (
<div className="space-y-2">
{active.map((item) => (
<MyListingRow key={item.id} item={item} />
))}
{sold.length > 0 && (
<>
<h4 className="pt-2 text-xs font-black uppercase tracking-widest text-gray-500">Sold</h4>
{sold.map((item) => (
<MyListingRow key={item.id} item={item} />
))}
</>
)}
</div>
)}
</div>
);
}