diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx
index 9b43724..0864140 100644
--- a/src/AppRouter.tsx
+++ b/src/AppRouter.tsx
@@ -27,7 +27,7 @@ const EditSpotPage = lazyWithRetry(() => import("./pages/EditSpotPage"));
const EditShopPage = lazyWithRetry(() => import("./pages/EditShopPage"));
const EditEventPage = lazyWithRetry(() => import("./pages/EditEventPage"));
const EditTourPage = lazyWithRetry(() => import("./pages/EditTourPage"));
-const GearSwapPage = lazyWithRetry(() => import("./pages/GearSwapPage"));
+const MarketplaceHubPage = lazyWithRetry(() => import("./pages/MarketplaceHubPage"));
const ListingDetailPage = lazyWithRetry(() => import("./pages/ListingDetailPage"));
const TradesPage = lazyWithRetry(() => import("./pages/TradesPage"));
const SpotDetailPage = lazyWithRetry(() => import("./pages/SpotDetailPage"));
@@ -125,7 +125,7 @@ export function AppRouter() {
} />
} />
} />
- } />
+ } />
} />
} />
} />
diff --git a/src/components/TradeReputationPanel.tsx b/src/components/TradeReputationPanel.tsx
new file mode 100644
index 0000000..6197f97
--- /dev/null
+++ b/src/components/TradeReputationPanel.tsx
@@ -0,0 +1,116 @@
+import { Link } from 'react-router-dom';
+import type { NostrEvent } from '@nostrify/nostrify';
+import { Star, ThumbsUp, ArrowUpRight } from 'lucide-react';
+import { Card, CardContent } from '@/components/ui/card';
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
+import { TradeReputationCard } from '@/components/TradeReputationCard';
+import { TradeRecommendationsCard } from '@/components/TradeRecommendationsCard';
+import { useTradeReputation, TRADE_METHODS, type TradeRole } from '@/hooks/useTradeAttestations';
+import { useAuthor } from '@/hooks/useAuthor';
+import { genUserName } from '@/lib/genUserName';
+import { cn } from '@/lib/utils';
+
+/** One trade record / review row. */
+function ReviewRow({ ev, selfPubkey }: { ev: NostrEvent; selfPubkey: string }) {
+ const role = ev.tags.find(([t]) => t === 'd')?.[1]?.split(':')[2] as TradeRole | undefined;
+ const counterparty = ev.tags.find(([t]) => t === 'p')?.[1] ?? '';
+ const method = ev.tags.find(([t]) => t === 'method')?.[1];
+ const amount = ev.tags.find(([t]) => t === 'amount')?.[1];
+ const coord = ev.tags.find(([t]) => t === 'a')?.[1] ?? '';
+ const [, listingPubkey, listingId] = coord.split(':');
+
+ const author = useAuthor(counterparty);
+ const name = author.data?.metadata?.name ?? genUserName(counterparty);
+ const picture = author.data?.metadata?.picture;
+
+ const given = ev.pubkey === selfPubkey;
+ const date = new Date(ev.created_at * 1000).toLocaleDateString();
+
+ return (
+
+
+
+
+ {name.slice(0, 2).toUpperCase()}
+
+
+
+
+
+ {name}
+
+
+ {role === 'sold' ? 'Sold' : 'Bought'}
+
+ {!given && ★ Review of you}
+ {given && your record}
+
+ {ev.content &&
{ev.content}
}
+
+ {method &&
{TRADE_METHODS[method as keyof typeof TRADE_METHODS] ?? method}}
+ {amount &&
{Number(amount).toLocaleString()} sats}
+
{date}
+ {listingPubkey && listingId && (
+
+ Listing
+
+ )}
+
+
+
+ );
+}
+
+/**
+ * Marketplace → Reputation tab: this trader's reputation, peer recommendations,
+ * and their trade records / reviews (from kind-31871 attestations).
+ */
+export function TradeReputationPanel({ pubkey }: { pubkey: string }) {
+ const { data, isLoading } = useTradeReputation(pubkey);
+ const attestations = [...(data?.attestations ?? [])].sort((a, b) => b.created_at - a.created_at);
+ const reviews = attestations.filter((a) => a.pubkey !== pubkey);
+
+ return (
+
+
+
+
+
+
+
+ Reviews & trade records
+ {reviews.length > 0 && (
+
+ {reviews.length} from others
+
+ )}
+
+
+ {isLoading ? (
+
Loading…
+ ) : attestations.length === 0 ? (
+
+
+
+
+ No trade records yet. Complete a deal and confirm it in My Trades — each
+ confirmation becomes a review on both profiles.
+
+
+
+ ) : (
+
+ {attestations.map((ev) => (
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/hooks/useNavPreferences.ts b/src/hooks/useNavPreferences.ts
index e5403e8..3e6a5d5 100644
--- a/src/hooks/useNavPreferences.ts
+++ b/src/hooks/useNavPreferences.ts
@@ -60,7 +60,7 @@ export const NAV_CATALOG: NavTab[] = [
{ id: 'crews', href: '/crews', labelKey: 'nav.crews', label: 'Crews', icon: Users },
{ id: 'wallet', href: '/wallet', labelKey: 'nav.wallet', label: 'Wallet', icon: Wallet, authOnly: true },
{ id: 'mints', href: '/mints', label: 'Mints', icon: Coins },
- { id: 'trades', href: '/trades', label: 'My Trades', icon: Handshake, authOnly: true },
+ { id: 'trades', href: '/marketplace', label: 'Marketplace', icon: Handshake },
{ id: 'stash', href: '/stash', label: 'Stash', icon: Package, authOnly: true },
{ id: 'collections', href: '/collections', label: 'Collections', icon: Bookmark, authOnly: true },
{ id: 'contacts', href: '/contacts', label: 'Contacts', icon: Users, authOnly: true },
diff --git a/src/pages/MarketplaceHubPage.tsx b/src/pages/MarketplaceHubPage.tsx
new file mode 100644
index 0000000..18d7db3
--- /dev/null
+++ b/src/pages/MarketplaceHubPage.tsx
@@ -0,0 +1,71 @@
+import { useState } from 'react';
+import { Store, Handshake, Star } from 'lucide-react';
+import GearSwapPage from '@/pages/GearSwapPage';
+import TradesPage from '@/pages/TradesPage';
+import { TradeReputationPanel } from '@/components/TradeReputationPanel';
+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: '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('browse');
+
+ return (
+ <>
+ {/* Tab bar — sticky under the app header, like other toolbars. */}
+
+
+
+ {TABS.map(({ id, label, icon: Icon }) => {
+ const active = tab === id;
+ return (
+
+ );
+ })}
+
+
+
+
+ {tab === 'browse' && }
+ {tab === 'trades' && }
+ {tab === 'reputation' && (
+
+ {user ? (
+
+ ) : (
+
+
Log in to see your reputation and reviews.
+
+
+ )}
+
+ )}
+ >
+ );
+}