- 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.
129 lines
5 KiB
TypeScript
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>
|
|
);
|
|
}
|