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`**
|
→ "Marketplace Offers". Trade/offer steps also feed **`useTradeNotifications`**
|
||||||
→ the bell's **All** tab (`TradeNotificationCard`, counted in the badge against
|
→ the bell's **All** tab (`TradeNotificationCard`, counted in the badge against
|
||||||
`seenAt`) and OS notifications (new **`trades`** category in `notificationPrefs`,
|
`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
|
- **Cashu escrow** (marketplace): time-locked **P2PK** (lock to seller + buyer
|
||||||
refund key + locktime) — see **`ESCROW.md`** and `src/lib/cashuEscrow.ts`
|
refund key + locktime) — see **`ESCROW.md`** and `src/lib/cashuEscrow.ts`
|
||||||
(unit-tested). Protocol: `cashu_token` on the `31872`; each user advertises a
|
(unit-tested). Protocol: `cashu_token` on the `31872`; each user advertises a
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { nip19 } from 'nostr-tools';
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
|
@ -347,7 +347,7 @@ export function NotificationBell({ className }: { className?: string }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs defaultValue="all" className="flex min-h-0 w-full flex-1 flex-col">
|
<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
|
<TabsTrigger
|
||||||
value="all"
|
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"
|
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
|
All
|
||||||
{badgeTotal > 0 && <span className="ml-1 text-teal-400">{badgeTotal}</span>}
|
{badgeTotal > 0 && <span className="ml-1 text-teal-400">{badgeTotal}</span>}
|
||||||
</TabsTrigger>
|
</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
|
<TabsTrigger
|
||||||
value="social"
|
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"
|
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>
|
</div>
|
||||||
</TabsContent>
|
</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 ──────────────────────────────────────────────── */}
|
{/* ─── SOCIAL ──────────────────────────────────────────────── */}
|
||||||
<TabsContent value="social" className="m-0 p-3">
|
<TabsContent value="social" className="m-0 p-3">
|
||||||
{activityLoading && activity.length === 0 && (
|
{activityLoading && activity.length === 0 && (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue