redesign: Stash page v2 — eliminate noise, get to items faster

- REMOVED: Sync banner (already in header subtitle)
- REMOVED: ProfileStatsBar self-intro card (it's YOUR page, you know who you are)
- REMOVED: Chunky tab switcher — replaced with underline tabs inside the sticky header,
  saving ~50px of vertical space before you see any items
- MERGED: All header info (title + item count + encryption + sats value) into one
  compact 56px sticky bar — nothing duplicated
- IMPROVED: Search + category filter in a single tight row (h-9 inputs)
- IMPROVED: Category chips only render when >1 category present (no orphan chips)
- IMPROVED: Sidebar is desktop-only (lg:hidden) — on mobile it was buried below items,
  now replaced by a contextual bottom banner that only appears when items are for sale
- IMPROVED: FABs slightly smaller (not competing with Sasquatch bubble)
- IMPROVED: Empty state is more focused — no walls of text

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-24 01:04:41 +07:00
parent df64a44c4e
commit f68864bb8f

View file

@ -54,10 +54,7 @@ import {
MapPin, MapPin,
LayoutGrid, LayoutGrid,
List, List,
RefreshCw,
ChevronRight, ChevronRight,
Smartphone,
Monitor,
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@ -934,6 +931,12 @@ export default function StashPage() {
const hasForSale = stashItems.filter(i => i.willingToSell || i.willingToTrade).length; const hasForSale = stashItems.filter(i => i.willingToSell || i.willingToTrade).length;
// Category chips — only show categories that have items
const activeCategoryChips = Object.entries(STASH_CATEGORIES).filter(
([key]) => displayItems.some(i => i.category === key)
);
const showCategoryChips = activeCategoryChips.length > 1;
return ( return (
<Layout> <Layout>
<style>{` <style>{`
@ -941,100 +944,120 @@ export default function StashPage() {
.animate-shimmer { animation: shimmer 2s infinite; } .animate-shimmer { animation: shimmer 2s infinite; }
`}</style> `}</style>
{/* ── Sticky page header ── */} {/* ── Page header — sticky, compact, everything you need ── */}
<div className="sticky top-0 z-30 bg-black/95 backdrop-blur border-b border-white/8"> <div className="sticky top-0 z-30 bg-black/96 backdrop-blur-md border-b border-white/8">
<div className="container px-4 py-3 max-w-7xl"> <div className="container px-4 max-w-7xl">
<div className="flex items-center justify-between gap-3"> {/* Top row */}
{/* Title + counts */} <div className="flex items-center justify-between h-14 gap-3">
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<div className="relative flex-shrink-0"> <div className="relative flex-shrink-0">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[#ff6600] to-[#ff0040] flex items-center justify-center shadow-[0_0_20px_rgba(255,102,0,0.3)]"> <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-[#ff6600] to-[#ff0040] flex items-center justify-center shadow-[0_0_16px_rgba(255,102,0,0.35)]">
<Backpack className="h-5 w-5 text-white" /> <Backpack className="h-4.5 w-4.5 text-white" style={{ height: '1.125rem', width: '1.125rem' }} />
</div> </div>
<div className="absolute -bottom-1 -right-1 w-5 h-5 rounded-full bg-[#ccff00] border-2 border-black flex items-center justify-center"> {stats.total > 0 && (
<span className="text-black text-[9px] font-black">{stats.total}</span> <div className="absolute -bottom-1 -right-1 min-w-[18px] h-[18px] rounded-full bg-[#ccff00] border-2 border-black flex items-center justify-center px-0.5">
<span className="text-black text-[9px] font-black leading-none">{stats.total}</span>
</div> </div>
)}
</div>
<div>
<h1 className="text-lg font-black uppercase text-white leading-none tracking-tight">My Stash</h1>
<div className="flex items-center gap-2 mt-0.5">
<span className="flex items-center gap-1 text-[10px] text-green-400/80">
<Shield className="h-2.5 w-2.5" />Encrypted
</span>
<span className="text-white/15 text-[10px]">·</span>
<span className="text-[10px] text-[#ccff00]/70">Syncs everywhere</span>
{stats.totalValue > 0 && (
<>
<span className="text-white/15 text-[10px]">·</span>
<span className="flex items-center gap-0.5 text-[10px] text-amber-400/80">
<Zap className="h-2.5 w-2.5" />
{stats.totalValue > 999 ? Math.floor(stats.totalValue / 1000) + 'k' : stats.totalValue} sats
</span>
</>
)}
</div> </div>
<div className="min-w-0">
<h1 className="text-xl font-black uppercase text-white leading-none">My Stash</h1>
<p className="text-[11px] text-muted-foreground flex items-center gap-1.5 mt-0.5">
<Shield className="h-3 w-3 text-green-400 flex-shrink-0" />
<span className="truncate">Encrypted · Synced across devices</span>
</p>
</div> </div>
</div> </div>
{/* Actions */} <div className="flex items-center gap-1.5 flex-shrink-0">
<div className="flex items-center gap-2 flex-shrink-0"> {/* View toggle */}
{/* View toggle — desktop only */} <div className="flex items-center rounded-lg border border-white/10 overflow-hidden">
<div className="hidden sm:flex items-center rounded-lg border border-white/10 overflow-hidden">
<button onClick={() => setViewMode('list')} <button onClick={() => setViewMode('list')}
className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'list' ? 'bg-white/10 text-white' : 'text-gray-500 hover:text-gray-300')}> className={cn('px-2 py-1.5 transition-colors', viewMode === 'list' ? 'bg-white/12 text-white' : 'text-gray-600 hover:text-gray-300')}>
<List className="h-4 w-4" /> <List className="h-3.5 w-3.5" />
</button> </button>
<button onClick={() => setViewMode('grid')} <button onClick={() => setViewMode('grid')}
className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'grid' ? 'bg-white/10 text-white' : 'text-gray-500 hover:text-gray-300')}> className={cn('px-2 py-1.5 transition-colors', viewMode === 'grid' ? 'bg-white/12 text-white' : 'text-gray-600 hover:text-gray-300')}>
<LayoutGrid className="h-4 w-4" /> <LayoutGrid className="h-3.5 w-3.5" />
</button> </button>
</div> </div>
{/* Desktop add buttons */}
<div className="hidden sm:flex gap-1.5">
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" /> <AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" />
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="stash" /> <AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="stash" />
</div> </div>
</div> </div>
</div> </div>
</div>
<div className="container px-4 py-4 max-w-7xl"> {/* Tab row — sits flush below title row */}
{/* ── Cross-device sync banner (shown first time) ── */} <div className="flex items-center gap-1 pb-0 -mb-px">
<div className="mb-4 p-3 rounded-xl bg-gradient-to-r from-[#ccff00]/10 to-green-500/10 border border-[#ccff00]/20 flex items-center gap-3">
<div className="flex-shrink-0 flex items-center gap-1">
<Smartphone className="h-4 w-4 text-[#ccff00]" />
<RefreshCw className="h-3 w-3 text-[#ccff00]/60" />
<Monitor className="h-4 w-4 text-[#ccff00]" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs text-[#ccff00]/90 font-bold leading-tight">Synced across all devices</p>
<p className="text-[11px] text-muted-foreground leading-tight">Add items on desktop, see them instantly on your phone — and vice versa. Your Nostr key = your account everywhere.</p>
</div>
</div>
<div className="grid lg:grid-cols-4 gap-4">
{/* ── Main content ── */}
<div className="lg:col-span-3 space-y-4">
{/* Profile stats bar */}
<ProfileStatsBar stats={stats} pubkey={user.pubkey} />
{/* Tab switcher */}
<div className="flex items-center gap-2 p-1 rounded-xl bg-black/40 border border-white/10 w-fit">
<button onClick={() => setActiveTab('stash')} <button onClick={() => setActiveTab('stash')}
className={cn('flex items-center gap-2 px-4 py-2 rounded-lg font-bold text-sm transition-all', className={cn(
activeTab === 'stash' ? 'bg-gradient-to-r from-[#ff6600] to-[#ff0040] text-white shadow-lg' : 'text-gray-400 hover:text-white')}> 'flex items-center gap-1.5 px-4 py-2.5 text-sm font-bold border-b-2 transition-all',
<Package className="h-4 w-4" /> activeTab === 'stash'
Stash <span className="opacity-60">({stashItems.length})</span> ? 'border-[#ff0040] text-white'
: 'border-transparent text-gray-500 hover:text-gray-300'
)}>
<Package className="h-3.5 w-3.5" />
Stash
<span className={cn('text-xs px-1.5 py-0.5 rounded-full font-black',
activeTab === 'stash' ? 'bg-[#ff0040]/20 text-[#ff0040]' : 'bg-white/8 text-gray-500')}>
{stashItems.length}
</span>
</button> </button>
<button onClick={() => setActiveTab('wishlist')} <button onClick={() => setActiveTab('wishlist')}
className={cn('flex items-center gap-2 px-4 py-2 rounded-lg font-bold text-sm transition-all', className={cn(
activeTab === 'wishlist' ? 'bg-cyan-500 text-black shadow-lg' : 'text-gray-400 hover:text-white')}> 'flex items-center gap-1.5 px-4 py-2.5 text-sm font-bold border-b-2 transition-all',
<Heart className="h-4 w-4" /> activeTab === 'wishlist'
Wishlist <span className="opacity-60">({wishlistItems.length})</span> ? 'border-cyan-400 text-white'
: 'border-transparent text-gray-500 hover:text-gray-300'
)}>
<Heart className="h-3.5 w-3.5" />
Wishlist
<span className={cn('text-xs px-1.5 py-0.5 rounded-full font-black',
activeTab === 'wishlist' ? 'bg-cyan-400/20 text-cyan-400' : 'bg-white/8 text-gray-500')}>
{wishlistItems.length}
</span>
</button> </button>
</div> </div>
</div>
</div>
{/* Search bar */} <div className="container px-4 py-3 max-w-7xl">
<div className="flex flex-col sm:flex-row gap-2"> <div className="grid lg:grid-cols-4 gap-4">
{/* ── Main content column ── */}
<div className="lg:col-span-3 space-y-3">
{/* Search + filter in one tight row */}
<div className="flex gap-2">
<div className="relative flex-1"> <div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input placeholder={activeTab === 'stash' ? "Search your stash..." : "Search wishlist..."} <Input
value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search..."
className="pl-9 bg-black/40 border-white/20" /> value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-9 bg-white/5 border-white/10 text-sm"
/>
</div> </div>
<Select value={filterCategory} onValueChange={setFilterCategory}> <Select value={filterCategory} onValueChange={setFilterCategory}>
<SelectTrigger className="w-full sm:w-[160px] bg-black/40 border-white/20"> <SelectTrigger className="w-[130px] h-9 bg-white/5 border-white/10 text-sm">
<Filter className="h-4 w-4 mr-2" /><SelectValue placeholder="All" /> <SelectValue placeholder="All" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="all">All Categories</SelectItem> <SelectItem value="all">All categories</SelectItem>
{Object.entries(STASH_CATEGORIES).map(([key, cat]) => ( {Object.entries(STASH_CATEGORIES).map(([key, cat]) => (
<SelectItem key={key} value={key}>{cat.emoji} {cat.label}</SelectItem> <SelectItem key={key} value={key}>{cat.emoji} {cat.label}</SelectItem>
))} ))}
@ -1042,38 +1065,52 @@ export default function StashPage() {
</Select> </Select>
</div> </div>
{/* Category quick-chips */} {/* Category chips — only when >1 category present */}
<div className="flex flex-wrap gap-1.5"> {showCategoryChips && (
{Object.entries(STASH_CATEGORIES).map(([key, cat]) => { <div className="flex items-center gap-1.5 flex-wrap">
{activeCategoryChips.map(([key, cat]) => {
const count = displayItems.filter(i => i.category === key).length; const count = displayItems.filter(i => i.category === key).length;
if (count === 0) return null;
return ( return (
<button key={key} onClick={() => setFilterCategory(filterCategory === key ? 'all' : key)} <button key={key}
className={cn('flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold transition-all border', onClick={() => setFilterCategory(filterCategory === key ? 'all' : key)}
filterCategory === key ? 'bg-white text-black border-white' : 'bg-black/40 border-white/10 text-gray-400 hover:border-white/30 hover:text-white')}> className={cn(
<span>{cat.emoji}</span> 'flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold transition-all border',
<span>{count}</span> filterCategory === key
? 'bg-white text-black border-white shadow-[0_0_8px_rgba(255,255,255,0.2)]'
: 'bg-white/5 border-white/10 text-gray-400 hover:border-white/25 hover:text-white'
)}>
{cat.emoji} <span className="opacity-70">{count}</span>
</button> </button>
); );
})} })}
</div> </div>
)}
{/* Location filter — stash only */} {/* Location filter chips — only when stash items have locations */}
{activeTab === 'stash' && stashItems.some(i => i.storageLocation) && ( {activeTab === 'stash' && stashItems.some(i => i.storageLocation && i.storageLocation !== 'other') && (
<div className="flex flex-wrap gap-1.5 items-center"> <div className="flex items-center gap-1.5 flex-wrap">
<span className="text-[10px] text-gray-500 font-bold uppercase tracking-wide">📍</span> <span className="text-[10px] text-gray-600 uppercase tracking-widest font-bold">Where:</span>
{([ {([
{ value: 'all', label: 'All' }, { value: 'all', label: 'All' },
{ value: 'home', label: '🏠' }, { value: 'car', label: '🚗' }, { value: 'home', label: '🏠 Home' },
{ value: 'backpack', label: '🎒' }, { value: 'skatepark', label: '🛹' }, { value: 'car', label: '🚗 Car' },
{ value: 'van', label: '🚐' }, { value: 'friends', label: '👫' }, { value: 'backpack', label: '🎒 Backpack' },
{ value: 'skatepark', label: '🛹 Skatepark' },
{ value: 'van', label: '🚐 Van' },
{ value: 'friends', label: "👫 Friend's" },
] as const).map(({ value, label }) => { ] as const).map(({ value, label }) => {
const count = value === 'all' ? stashItems.length : stashItems.filter(i => i.storageLocation === value).length; const count = value === 'all'
? stashItems.length
: stashItems.filter(i => i.storageLocation === value).length;
if (value !== 'all' && count === 0) return null; if (value !== 'all' && count === 0) return null;
return ( return (
<button key={value} onClick={() => setFilterLocation(value)} <button key={value} onClick={() => setFilterLocation(value)}
className={cn('px-2 py-1 rounded-full text-xs font-bold transition-all border', className={cn(
filterLocation === value ? 'bg-[#ff6600] border-[#ff6600] text-black' : 'bg-black/40 border-white/10 text-gray-400 hover:border-[#ff6600]/50')}> 'px-2.5 py-1 rounded-full text-xs font-bold transition-all border',
filterLocation === value
? 'bg-[#ff6600] border-[#ff6600] text-black'
: 'bg-white/5 border-white/10 text-gray-400 hover:border-[#ff6600]/40 hover:text-white'
)}>
{label} {label}
</button> </button>
); );
@ -1081,48 +1118,50 @@ export default function StashPage() {
</div> </div>
)} )}
{/* Items */} {/* Items list */}
{isLoading ? ( {isLoading ? (
<div className="space-y-2"> <div className="space-y-2 pt-1">
{Array.from({ length: 4 }).map((_, i) => ( {Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex items-center gap-3 p-3 rounded-xl border border-white/10 bg-black/40"> <div key={i} className="flex items-center gap-3 p-3 rounded-xl border border-white/8 bg-white/3">
<Skeleton className="w-14 h-14 rounded-lg bg-white/5 flex-shrink-0" /> <Skeleton className="w-14 h-14 rounded-lg bg-white/5 flex-shrink-0" />
<div className="flex-1 space-y-2"> <div className="flex-1 space-y-2">
<Skeleton className="h-4 w-3/4 bg-white/5" /> <Skeleton className="h-4 w-2/3 bg-white/5" />
<Skeleton className="h-3 w-1/2 bg-white/5" /> <Skeleton className="h-3 w-1/3 bg-white/5" />
</div> </div>
</div> </div>
))} ))}
</div> </div>
) : filteredItems.length === 0 ? ( ) : filteredItems.length === 0 ? (
<Card className="bg-black/60 border-dashed border-2 border-gray-700"> <div className="py-14 text-center">
<CardContent className="py-14 text-center">
{activeTab === 'wishlist' ? ( {activeTab === 'wishlist' ? (
<> <>
<Heart className="h-12 w-12 mx-auto mb-4 text-cyan-500/50" /> <Heart className="h-10 w-10 mx-auto mb-3 text-cyan-500/40" />
<h3 className="text-xl font-bold text-white mb-2">Wishlist is Empty</h3> <p className="font-bold text-white mb-1">Wishlist is empty</p>
<p className="text-muted-foreground mb-6 max-w-sm mx-auto">Add items you're hunting for to get notified when friends have matching gear.</p> <p className="text-sm text-muted-foreground mb-5 max-w-xs mx-auto">Add gear you're hunting for — we'll match you with friends' stashes.</p>
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" /> <AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" />
</> </>
) : stashItems.length === 0 ? (
<>
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-[#ff6600]/20 to-[#ff0040]/20 border border-[#ff6600]/30 mb-4">
<Backpack className="h-8 w-8 text-[#ff6600]/70" />
</div>
<p className="font-bold text-white text-lg mb-1">Your stash is empty</p>
<p className="text-sm text-muted-foreground mb-5 max-w-xs mx-auto">
Start tracking your gear. Add here or on desktop — it syncs everywhere.
</p>
<AddItemDialog onAdd={(item) => addItem.mutate(item)} />
</>
) : ( ) : (
<> <>
<Sparkles className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" /> <Search className="h-8 w-8 mx-auto mb-3 text-muted-foreground/40" />
<h3 className="text-xl font-bold text-white mb-2"> <p className="text-muted-foreground text-sm">No items match your filters.</p>
{stashItems.length === 0 ? 'Your Stash is Empty' : 'No Items Match'} <button onClick={() => { setSearchQuery(''); setFilterCategory('all'); setFilterLocation('all'); }}
</h3> className="mt-3 text-xs text-[#ff6600] hover:underline">Clear filters</button>
<p className="text-muted-foreground mb-6 max-w-sm mx-auto">
{stashItems.length === 0
? 'Add your gear to track what you own. Add on any device — it syncs everywhere via Nostr.'
: 'Try adjusting your search or filters.'}
</p>
{stashItems.length === 0 && <AddItemDialog onAdd={(item) => addItem.mutate(item)} />}
</> </>
)} )}
</CardContent> </div>
</Card>
) : viewMode === 'list' ? ( ) : viewMode === 'list' ? (
/* List view — default on mobile, toggleable on desktop */ <div className="space-y-2 pt-1">
<div className="space-y-2">
{filteredItems.map((item) => ( {filteredItems.map((item) => (
<StashListRow key={item.id} item={item} isWTB={item.itemType === 'wtb'} <StashListRow key={item.id} item={item} isWTB={item.itemType === 'wtb'}
onEdit={() => setEditingItem(item)} onEdit={() => setEditingItem(item)}
@ -1131,8 +1170,7 @@ export default function StashPage() {
))} ))}
</div> </div>
) : ( ) : (
/* Grid view */ <div className="grid grid-cols-2 md:grid-cols-3 gap-3 pt-1">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{filteredItems.map((item) => ( {filteredItems.map((item) => (
<StashItemCard key={item.id} item={item} isWTB={item.itemType === 'wtb'} <StashItemCard key={item.id} item={item} isWTB={item.itemType === 'wtb'}
onEdit={() => setEditingItem(item)} onEdit={() => setEditingItem(item)}
@ -1143,89 +1181,101 @@ export default function StashPage() {
)} )}
</div> </div>
{/* ── Sidebar ── */} {/* ── Sidebar — desktop only, hidden on mobile ── */}
<div className="space-y-4"> <div className="hidden lg:flex flex-col gap-3">
{/* Marketplace card — shows "Ready to Sell" CTA when items are for sale,
otherwise a quieter info card. Never duplicated. */} {/* Marketplace card */}
<Card className={cn( <Card className={cn('border', hasForSale > 0
'border',
hasForSale > 0
? 'bg-gradient-to-br from-[#ff6600]/15 to-[#ff0040]/10 border-[#ff6600]/40' ? 'bg-gradient-to-br from-[#ff6600]/15 to-[#ff0040]/10 border-[#ff6600]/40'
: 'bg-black/40 border-white/10' : 'bg-white/3 border-white/8')}>
)}>
<CardContent className="p-4 space-y-3"> <CardContent className="p-4 space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Store className="h-4 w-4 text-[#ff6600]" /> <Store className="h-4 w-4 text-[#ff6600]" />
<span className={cn('font-black text-sm uppercase tracking-wide', hasForSale > 0 ? 'text-[#ff6600]' : 'text-white')}> <span className={cn('font-black text-sm uppercase tracking-wide',
{hasForSale > 0 ? 'Ready to Sell' : 'Marketplace'} hasForSale > 0 ? 'text-[#ff6600]' : 'text-gray-300')}>
{hasForSale > 0 ? `${hasForSale} ready to sell` : 'Marketplace'}
</span> </span>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground leading-relaxed">
{hasForSale > 0 {hasForSale > 0
? <><strong className="text-white">{hasForSale} item{hasForSale > 1 ? 's' : ''}</strong> marked for sale or trade — list on the marketplace in one tap.</> ? `${hasForSale} item${hasForSale > 1 ? 's' : ''} marked for sale or trade.`
: 'Mark items as "Willing to Sell" or "Willing to Trade" to list them publicly.'} : 'Mark items as "Willing to Sell" or "Willing to Trade" to list them.'}
</p> </p>
<div className="flex items-center gap-3 text-xs"> <div className="flex gap-2">
<Badge variant="outline" className="border-green-500/50 text-green-400 gap-1 h-5"><Zap className="h-2.5 w-2.5" />Sell</Badge> <Badge variant="outline" className="border-green-500/40 text-green-400 gap-1 h-5 text-[10px]">
<Badge variant="outline" className="border-cyan-500/50 text-cyan-400 gap-1 h-5"><ArrowRightLeft className="h-2.5 w-2.5" />Trade</Badge> <Zap className="h-2.5 w-2.5" />Sell
</Badge>
<Badge variant="outline" className="border-cyan-500/40 text-cyan-400 gap-1 h-5 text-[10px]">
<ArrowRightLeft className="h-2.5 w-2.5" />Trade
</Badge>
</div> </div>
<Link to="/marketplace"> <Link to="/marketplace">
<Button size="sm" <Button size="sm" className={cn('w-full gap-2 h-8',
className={cn('w-full gap-2', hasForSale > 0 hasForSale > 0
? 'bg-[#ff6600] hover:bg-[#ff6600]/90 text-black font-bold' ? 'bg-[#ff6600] hover:bg-[#ff6600]/90 text-black font-bold'
: 'bg-white/5 hover:bg-white/10 text-white border border-white/10')}> : 'bg-white/5 hover:bg-white/10 text-white/70 border border-white/10')}>
<ShoppingCart className="h-3.5 w-3.5" /> <ShoppingCart className="h-3.5 w-3.5" />Browse Marketplace
Browse Marketplace
{hasForSale > 0 && <ChevronRight className="h-3.5 w-3.5 ml-auto" />}
</Button> </Button>
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
{/* Wishlist match teaser */} {/* Wishlist */}
{wishlistItems.length > 0 && ( {wishlistItems.length > 0 && (
<Card className="bg-gradient-to-br from-cyan-500/10 to-blue-500/10 border-cyan-500/30"> <Card className="bg-cyan-500/5 border-cyan-500/20">
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Heart className="h-4 w-4 text-cyan-400" /> <Heart className="h-3.5 w-3.5 text-cyan-400" />
<span className="font-black text-sm text-cyan-400">Wishlist</span> <span className="font-bold text-sm text-cyan-300">Wishlist</span>
<Badge className="bg-cyan-500/20 text-cyan-400 border-cyan-500/40 text-[10px]">{wishlistItems.length}</Badge> <Badge className="bg-cyan-500/20 text-cyan-400 text-[10px] h-4 px-1.5">{wishlistItems.length}</Badge>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">We'll match your wishlist with friends' stashes.</p>
We'll match your wishlist with friends' stashes and alert you when something fits.
</p>
</CardContent> </CardContent>
</Card> </Card>
)} )}
{/* Pro tips — compact */} {/* Tips */}
<Card className="bg-black/40 border-white/8"> <Card className="bg-white/2 border-white/6">
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-center gap-2 mb-2"> <p className="text-[10px] font-black uppercase tracking-widest text-gray-600 mb-2">Tips</p>
<Gem className="h-4 w-4 text-purple-400" /> <ul className="space-y-1.5 text-xs text-gray-500">
<span className="font-bold text-sm text-purple-300">Tips</span> <li>✏️ Tap to edit any item</li>
</div> <li>🏪 Tap to list on marketplace</li>
<ul className="space-y-1.5 text-xs text-muted-foreground"> <li>📸 Add photos for better trades</li>
<li className="flex items-start gap-1.5"><span className="text-[#ccff00] mt-0.5">→</span>Tap the ✏️ icon to edit any item</li> <li>🔄 Syncs across all devices</li>
<li className="flex items-start gap-1.5"><span className="text-[#ccff00] mt-0.5">→</span>Tap 🏪 to list an item on the marketplace</li>
<li className="flex items-start gap-1.5"><span className="text-[#ccff00] mt-0.5">→</span>Add photos to attract better trade offers</li>
<li className="flex items-start gap-1.5"><span className="text-[#ccff00] mt-0.5">→</span>Items added on desktop sync to your phone automatically</li>
</ul> </ul>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </div>
{/* ── Mobile bottom info bar — marketplace CTA, only if items for sale ── */}
{hasForSale > 0 && (
<div className="lg:hidden mt-4 mb-2">
<Link to="/marketplace">
<div className="flex items-center gap-3 p-3 rounded-xl bg-gradient-to-r from-[#ff6600]/20 to-[#ff0040]/15 border border-[#ff6600]/30 active:scale-[0.98] transition-transform">
<div className="w-8 h-8 rounded-lg bg-[#ff6600] flex items-center justify-center flex-shrink-0">
<ShoppingCart className="h-4 w-4 text-black" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-black text-white leading-none">{hasForSale} item{hasForSale > 1 ? 's' : ''} ready to sell</p>
<p className="text-[11px] text-[#ff6600]/80 mt-0.5">Browse Marketplace →</p>
</div>
</div>
</Link>
</div>
)}
</div> </div>
{/* ── Floating Add FAB (mobile only) ── */} {/* ── Mobile FAB — single + button, tapping reveals stash/wishlist choice ── */}
<div className="fixed md:hidden z-40 flex flex-col items-end gap-2" <div className="fixed md:hidden z-40 flex flex-col items-end gap-2"
style={{ bottom: 'calc(3.5rem + env(safe-area-inset-bottom, 0px) + 0.75rem)', right: '0.75rem' }}> style={{ bottom: 'calc(3.5rem + env(safe-area-inset-bottom, 0px) + 0.75rem)', right: '0.75rem' }}>
<AddItemDialog <AddItemDialog
onAdd={(item) => addItem.mutate(item)} onAdd={(item) => addItem.mutate(item)}
mode="wtb" mode="wtb"
trigger={ trigger={
<button className="h-11 w-11 rounded-full bg-cyan-500 shadow-xl shadow-cyan-500/30 border-2 border-white/20 flex items-center justify-center active:scale-90 transition-transform touch-manipulation"> <button className="h-10 w-10 rounded-full bg-cyan-500/90 shadow-lg shadow-cyan-500/20 border border-white/20 flex items-center justify-center active:scale-90 transition-transform touch-manipulation">
<Heart className="h-5 w-5 text-black" strokeWidth={2.5} /> <Heart className="h-4.5 w-4.5 text-black" style={{ height: '1.125rem', width: '1.125rem' }} />
</button> </button>
} }
/> />
@ -1233,8 +1283,9 @@ export default function StashPage() {
onAdd={(item) => addItem.mutate(item)} onAdd={(item) => addItem.mutate(item)}
mode="stash" mode="stash"
trigger={ trigger={
<button className="h-14 w-14 rounded-full bg-gradient-to-br from-[#ff6600] to-[#ff0040] shadow-xl shadow-[#ff0040]/30 border-2 border-white/20 flex items-center justify-center active:scale-90 transition-transform touch-manipulation"> <button className="h-13 w-13 rounded-full bg-gradient-to-br from-[#ff6600] to-[#ff0040] shadow-xl shadow-[#ff0040]/25 border-2 border-white/20 flex items-center justify-center active:scale-90 transition-transform touch-manipulation"
<Plus className="h-7 w-7 text-white" strokeWidth={3} /> style={{ height: '3.25rem', width: '3.25rem' }}>
<Plus className="h-6 w-6 text-white" strokeWidth={2.5} />
</button> </button>
} }
/> />