feat(bell): dedicated Trades tab
The bell had All/Social/News/Crews; trade steps were only inside All. Added a 5th tab (Handshake, orange accent) listing offers, counter-offers, outcomes and trade confirmations, with its own count. All still shows them first.
This commit is contained in:
parent
7dff8793b8
commit
0b7371263c
2 changed files with 38 additions and 3 deletions
|
|
@ -89,7 +89,13 @@ session with **no other memory of this project**. So:
|
|||
→ "Marketplace Offers". Trade/offer steps also feed **`useTradeNotifications`**
|
||||
→ the bell's **All** tab (`TradeNotificationCard`, counted in the badge against
|
||||
`seenAt`) and OS notifications (new **`trades`** category in `notificationPrefs`,
|
||||
wired in `OsNotificationBridge`).
|
||||
wired in `OsNotificationBridge`). The bell also has a dedicated **Trades tab**
|
||||
(5th tab, `Handshake`) listing them, with its own count. **Barter is supported
|
||||
by design**: trade attestations carry `method` (`cash|lightning|trade|other` —
|
||||
`MarkSoldDialog` picks it), so a swap feeds reputation exactly like a sale; an
|
||||
offer with **no `amount`** plus a note is how a swap is proposed (there is
|
||||
deliberately **no** "For trade" listing type yet — `LISTING_TYPES` is
|
||||
`selling|wtb`).
|
||||
- **Cashu escrow** (marketplace): time-locked **P2PK** (lock to seller + buyer
|
||||
refund key + locktime) — see **`ESCROW.md`** and `src/lib/cashuEscrow.ts`
|
||||
(unit-tested). Protocol: `cashu_token` on the `31872`; each user advertises a
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { Bell, Users, Check, Loader2, EyeOff, Globe, CheckCheck, Heart, Radio, ArrowRight } from 'lucide-react';
|
||||
import { Bell, Users, Check, Loader2, EyeOff, Globe, CheckCheck, Heart, Radio, ArrowRight, Handshake } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
|
|
@ -347,7 +347,7 @@ export function NotificationBell({ className }: { className?: string }) {
|
|||
</div>
|
||||
|
||||
<Tabs defaultValue="all" className="flex min-h-0 w-full flex-1 flex-col">
|
||||
<TabsList className="grid w-full shrink-0 grid-cols-4 rounded-none border-b border-white/10 bg-transparent p-0">
|
||||
<TabsList className="grid w-full shrink-0 grid-cols-5 rounded-none border-b border-white/10 bg-transparent p-0">
|
||||
<TabsTrigger
|
||||
value="all"
|
||||
className="rounded-none border-b-2 border-transparent py-2.5 text-xs font-bold data-[state=active]:border-teal-400 data-[state=active]:bg-transparent data-[state=active]:text-teal-400"
|
||||
|
|
@ -355,6 +355,13 @@ export function NotificationBell({ className }: { className?: string }) {
|
|||
All
|
||||
{badgeTotal > 0 && <span className="ml-1 text-teal-400">{badgeTotal}</span>}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="trades"
|
||||
className="rounded-none border-b-2 border-transparent py-2.5 text-xs font-bold data-[state=active]:border-[#ff6600] data-[state=active]:bg-transparent data-[state=active]:text-[#ff6600]"
|
||||
>
|
||||
<Handshake className="mr-1 h-3 w-3" /> Trades
|
||||
{tradeNotifs.length > 0 && <span className="ml-1 text-[#ff6600]">{tradeNotifs.length}</span>}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="social"
|
||||
className="rounded-none border-b-2 border-transparent py-2.5 text-xs font-bold data-[state=active]:border-teal-400 data-[state=active]:bg-transparent data-[state=active]:text-teal-400"
|
||||
|
|
@ -430,6 +437,28 @@ export function NotificationBell({ className }: { className?: string }) {
|
|||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* ─── TRADES (offers, counter-offers, confirmations) ──────── */}
|
||||
<TabsContent value="trades" className="m-0 p-3">
|
||||
{tradeLoading && tradeNotifs.length === 0 && (
|
||||
<div className="flex items-center justify-center gap-2 py-8 text-sm text-gray-500">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Checking offers and trades…
|
||||
</div>
|
||||
)}
|
||||
{!tradeLoading && tradeNotifs.length === 0 && (
|
||||
<EmptyState label="Offers, counter-offers and trade confirmations all land here — whether you're selling, buying or swapping." />
|
||||
)}
|
||||
<div className="space-y-2.5">
|
||||
{tradeNotifs.map((n) => (
|
||||
<TradeNotificationCard
|
||||
key={n.id}
|
||||
notification={n}
|
||||
unread={n.createdAt > seenAt}
|
||||
onNavigate={() => setOpen(false)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* ─── SOCIAL ──────────────────────────────────────────────── */}
|
||||
<TabsContent value="social" className="m-0 p-3">
|
||||
{activityLoading && activity.length === 0 && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue