- 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.
85 lines
3.1 KiB
TypeScript
85 lines
3.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { Store, Handshake, Star, Package } from 'lucide-react';
|
|
import GearSwapPage from '@/pages/GearSwapPage';
|
|
import TradesPage from '@/pages/TradesPage';
|
|
import { TradeReputationPanel } from '@/components/TradeReputationPanel';
|
|
import { SellerDashboard } from '@/components/SellerDashboard';
|
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|
import { LoginArea } from '@/components/auth/LoginArea';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const TABS = [
|
|
{ id: 'browse', label: 'Browse', icon: Store },
|
|
{ id: 'selling', label: 'Selling', icon: Package },
|
|
{ id: 'trades', label: 'My Trades', icon: Handshake },
|
|
{ id: 'reputation', label: 'Reputation', icon: Star },
|
|
] as const;
|
|
|
|
type TabId = (typeof TABS)[number]['id'];
|
|
|
|
/**
|
|
* Marketplace hub — one home for buying/selling: the gear grid, your trades and
|
|
* your reputation/reviews, as tabs. `/trades` and `/listing/...` deep links keep
|
|
* working; this is just the front door.
|
|
*/
|
|
export default function MarketplaceHubPage() {
|
|
const { user } = useCurrentUser();
|
|
const [tab, setTab] = useState<TabId>('browse');
|
|
|
|
return (
|
|
<>
|
|
{/* Tab bar — sticky under the app header, like other toolbars. */}
|
|
<div className="sticky top-14 z-30 border-b border-white/5 bg-black/95 backdrop-blur md:top-16">
|
|
<div className="container max-w-7xl px-4">
|
|
<div className="scroll-container flex gap-1 overflow-x-auto py-2">
|
|
{TABS.map(({ id, label, icon: Icon }) => {
|
|
const active = tab === id;
|
|
return (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
onClick={() => setTab(id)}
|
|
aria-pressed={active}
|
|
className={cn(
|
|
'flex shrink-0 items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-bold uppercase tracking-wide transition-colors',
|
|
active ? 'bg-[#ccff00] text-black' : 'text-gray-400 hover:bg-white/5 hover:text-white',
|
|
)}
|
|
>
|
|
<Icon className="h-3.5 w-3.5" />
|
|
{label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{tab === 'browse' && <GearSwapPage />}
|
|
{tab === 'selling' && (
|
|
<div className="container mx-auto max-w-3xl px-4 py-8">
|
|
{user ? (
|
|
<SellerDashboard />
|
|
) : (
|
|
<div className="space-y-4 py-16 text-center">
|
|
<p className="text-gray-400">Log in to manage your listings.</p>
|
|
<LoginArea className="justify-center" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
{tab === 'trades' && <TradesPage />}
|
|
{tab === 'reputation' && (
|
|
<div className="container mx-auto max-w-3xl px-4 py-8">
|
|
{user ? (
|
|
<TradeReputationPanel pubkey={user.pubkey} />
|
|
) : (
|
|
<div className="space-y-4 py-16 text-center">
|
|
<p className="text-gray-400">Log in to see your reputation and reviews.</p>
|
|
<LoginArea className="justify-center" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|