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:
parent
df64a44c4e
commit
f68864bb8f
1 changed files with 230 additions and 179 deletions
|
|
@ -54,10 +54,7 @@ import {
|
|||
MapPin,
|
||||
LayoutGrid,
|
||||
List,
|
||||
RefreshCw,
|
||||
ChevronRight,
|
||||
Smartphone,
|
||||
Monitor,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
|
@ -934,6 +931,12 @@ export default function StashPage() {
|
|||
|
||||
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 (
|
||||
<Layout>
|
||||
<style>{`
|
||||
|
|
@ -941,100 +944,120 @@ export default function StashPage() {
|
|||
.animate-shimmer { animation: shimmer 2s infinite; }
|
||||
`}</style>
|
||||
|
||||
{/* ── Sticky page header ── */}
|
||||
<div className="sticky top-0 z-30 bg-black/95 backdrop-blur border-b border-white/8">
|
||||
<div className="container px-4 py-3 max-w-7xl">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{/* Title + counts */}
|
||||
{/* ── Page header — sticky, compact, everything you need ── */}
|
||||
<div className="sticky top-0 z-30 bg-black/96 backdrop-blur-md border-b border-white/8">
|
||||
<div className="container px-4 max-w-7xl">
|
||||
{/* Top row */}
|
||||
<div className="flex items-center justify-between h-14 gap-3">
|
||||
<div className="flex items-center gap-3 min-w-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)]">
|
||||
<Backpack className="h-5 w-5 text-white" />
|
||||
</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">
|
||||
<span className="text-black text-[9px] font-black">{stats.total}</span>
|
||||
<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-4.5 w-4.5 text-white" style={{ height: '1.125rem', width: '1.125rem' }} />
|
||||
</div>
|
||||
{stats.total > 0 && (
|
||||
<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 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>
|
||||
<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>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{/* View toggle — desktop only */}
|
||||
<div className="hidden sm:flex items-center rounded-lg border border-white/10 overflow-hidden">
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{/* View toggle */}
|
||||
<div className="flex items-center rounded-lg border border-white/10 overflow-hidden">
|
||||
<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')}>
|
||||
<List className="h-4 w-4" />
|
||||
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-3.5 w-3.5" />
|
||||
</button>
|
||||
<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')}>
|
||||
<LayoutGrid className="h-4 w-4" />
|
||||
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-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" />
|
||||
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="stash" />
|
||||
{/* 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="stash" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tab row — sits flush below title row */}
|
||||
<div className="flex items-center gap-1 pb-0 -mb-px">
|
||||
<button onClick={() => setActiveTab('stash')}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-4 py-2.5 text-sm font-bold border-b-2 transition-all',
|
||||
activeTab === 'stash'
|
||||
? '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 onClick={() => setActiveTab('wishlist')}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-4 py-2.5 text-sm font-bold border-b-2 transition-all',
|
||||
activeTab === 'wishlist'
|
||||
? '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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container px-4 py-4 max-w-7xl">
|
||||
{/* ── Cross-device sync banner (shown first time) ── */}
|
||||
<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="container px-4 py-3 max-w-7xl">
|
||||
<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} />
|
||||
{/* ── Main content column ── */}
|
||||
<div className="lg:col-span-3 space-y-3">
|
||||
|
||||
{/* 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')}
|
||||
className={cn('flex items-center gap-2 px-4 py-2 rounded-lg font-bold text-sm transition-all',
|
||||
activeTab === 'stash' ? 'bg-gradient-to-r from-[#ff6600] to-[#ff0040] text-white shadow-lg' : 'text-gray-400 hover:text-white')}>
|
||||
<Package className="h-4 w-4" />
|
||||
Stash <span className="opacity-60">({stashItems.length})</span>
|
||||
</button>
|
||||
<button onClick={() => setActiveTab('wishlist')}
|
||||
className={cn('flex items-center gap-2 px-4 py-2 rounded-lg font-bold text-sm transition-all',
|
||||
activeTab === 'wishlist' ? 'bg-cyan-500 text-black shadow-lg' : 'text-gray-400 hover:text-white')}>
|
||||
<Heart className="h-4 w-4" />
|
||||
Wishlist <span className="opacity-60">({wishlistItems.length})</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search bar */}
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
{/* Search + filter in one tight row */}
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder={activeTab === 'stash' ? "Search your stash..." : "Search wishlist..."}
|
||||
value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 bg-black/40 border-white/20" />
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9 h-9 bg-white/5 border-white/10 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Select value={filterCategory} onValueChange={setFilterCategory}>
|
||||
<SelectTrigger className="w-full sm:w-[160px] bg-black/40 border-white/20">
|
||||
<Filter className="h-4 w-4 mr-2" /><SelectValue placeholder="All" />
|
||||
<SelectTrigger className="w-[130px] h-9 bg-white/5 border-white/10 text-sm">
|
||||
<SelectValue placeholder="All" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Categories</SelectItem>
|
||||
<SelectItem value="all">All categories</SelectItem>
|
||||
{Object.entries(STASH_CATEGORIES).map(([key, cat]) => (
|
||||
<SelectItem key={key} value={key}>{cat.emoji} {cat.label}</SelectItem>
|
||||
))}
|
||||
|
|
@ -1042,38 +1065,52 @@ export default function StashPage() {
|
|||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Category quick-chips */}
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{Object.entries(STASH_CATEGORIES).map(([key, cat]) => {
|
||||
const count = displayItems.filter(i => i.category === key).length;
|
||||
if (count === 0) return null;
|
||||
return (
|
||||
<button key={key} onClick={() => setFilterCategory(filterCategory === key ? 'all' : key)}
|
||||
className={cn('flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold transition-all border',
|
||||
filterCategory === key ? 'bg-white text-black border-white' : 'bg-black/40 border-white/10 text-gray-400 hover:border-white/30 hover:text-white')}>
|
||||
<span>{cat.emoji}</span>
|
||||
<span>{count}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Category chips — only when >1 category present */}
|
||||
{showCategoryChips && (
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{activeCategoryChips.map(([key, cat]) => {
|
||||
const count = displayItems.filter(i => i.category === key).length;
|
||||
return (
|
||||
<button key={key}
|
||||
onClick={() => setFilterCategory(filterCategory === key ? 'all' : key)}
|
||||
className={cn(
|
||||
'flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold transition-all border',
|
||||
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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Location filter — stash only */}
|
||||
{activeTab === 'stash' && stashItems.some(i => i.storageLocation) && (
|
||||
<div className="flex flex-wrap gap-1.5 items-center">
|
||||
<span className="text-[10px] text-gray-500 font-bold uppercase tracking-wide">📍</span>
|
||||
{/* Location filter chips — only when stash items have locations */}
|
||||
{activeTab === 'stash' && stashItems.some(i => i.storageLocation && i.storageLocation !== 'other') && (
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-[10px] text-gray-600 uppercase tracking-widest font-bold">Where:</span>
|
||||
{([
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'home', label: '🏠' }, { value: 'car', label: '🚗' },
|
||||
{ value: 'backpack', label: '🎒' }, { value: 'skatepark', label: '🛹' },
|
||||
{ value: 'van', label: '🚐' }, { value: 'friends', label: '👫' },
|
||||
{ value: 'home', label: '🏠 Home' },
|
||||
{ value: 'car', label: '🚗 Car' },
|
||||
{ value: 'backpack', label: '🎒 Backpack' },
|
||||
{ value: 'skatepark', label: '🛹 Skatepark' },
|
||||
{ value: 'van', label: '🚐 Van' },
|
||||
{ value: 'friends', label: "👫 Friend's" },
|
||||
] 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;
|
||||
return (
|
||||
<button key={value} onClick={() => setFilterLocation(value)}
|
||||
className={cn('px-2 py-1 rounded-full text-xs font-bold transition-all border',
|
||||
filterLocation === value ? 'bg-[#ff6600] border-[#ff6600] text-black' : 'bg-black/40 border-white/10 text-gray-400 hover:border-[#ff6600]/50')}>
|
||||
className={cn(
|
||||
'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}
|
||||
</button>
|
||||
);
|
||||
|
|
@ -1081,48 +1118,50 @@ export default function StashPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Items */}
|
||||
{/* Items list */}
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-2 pt-1">
|
||||
{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" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-3/4 bg-white/5" />
|
||||
<Skeleton className="h-3 w-1/2 bg-white/5" />
|
||||
<Skeleton className="h-4 w-2/3 bg-white/5" />
|
||||
<Skeleton className="h-3 w-1/3 bg-white/5" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : filteredItems.length === 0 ? (
|
||||
<Card className="bg-black/60 border-dashed border-2 border-gray-700">
|
||||
<CardContent className="py-14 text-center">
|
||||
{activeTab === 'wishlist' ? (
|
||||
<>
|
||||
<Heart className="h-12 w-12 mx-auto mb-4 text-cyan-500/50" />
|
||||
<h3 className="text-xl font-bold text-white mb-2">Wishlist is Empty</h3>
|
||||
<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>
|
||||
<AddItemDialog onAdd={(item) => addItem.mutate(item)} mode="wtb" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
{stashItems.length === 0 ? 'Your Stash is Empty' : 'No Items Match'}
|
||||
</h3>
|
||||
<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>
|
||||
</Card>
|
||||
<div className="py-14 text-center">
|
||||
{activeTab === 'wishlist' ? (
|
||||
<>
|
||||
<Heart className="h-10 w-10 mx-auto mb-3 text-cyan-500/40" />
|
||||
<p className="font-bold text-white mb-1">Wishlist is empty</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" />
|
||||
</>
|
||||
) : 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)} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-8 w-8 mx-auto mb-3 text-muted-foreground/40" />
|
||||
<p className="text-muted-foreground text-sm">No items match your filters.</p>
|
||||
<button onClick={() => { setSearchQuery(''); setFilterCategory('all'); setFilterLocation('all'); }}
|
||||
className="mt-3 text-xs text-[#ff6600] hover:underline">Clear filters</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : viewMode === 'list' ? (
|
||||
/* List view — default on mobile, toggleable on desktop */
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-2 pt-1">
|
||||
{filteredItems.map((item) => (
|
||||
<StashListRow key={item.id} item={item} isWTB={item.itemType === 'wtb'}
|
||||
onEdit={() => setEditingItem(item)}
|
||||
|
|
@ -1131,8 +1170,7 @@ export default function StashPage() {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
/* Grid view */
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 pt-1">
|
||||
{filteredItems.map((item) => (
|
||||
<StashItemCard key={item.id} item={item} isWTB={item.itemType === 'wtb'}
|
||||
onEdit={() => setEditingItem(item)}
|
||||
|
|
@ -1143,89 +1181,101 @@ export default function StashPage() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Sidebar ── */}
|
||||
<div className="space-y-4">
|
||||
{/* Marketplace card — shows "Ready to Sell" CTA when items are for sale,
|
||||
otherwise a quieter info card. Never duplicated. */}
|
||||
<Card className={cn(
|
||||
'border',
|
||||
hasForSale > 0
|
||||
? 'bg-gradient-to-br from-[#ff6600]/15 to-[#ff0040]/10 border-[#ff6600]/40'
|
||||
: 'bg-black/40 border-white/10'
|
||||
)}>
|
||||
{/* ── Sidebar — desktop only, hidden on mobile ── */}
|
||||
<div className="hidden lg:flex flex-col gap-3">
|
||||
|
||||
{/* Marketplace card */}
|
||||
<Card className={cn('border', hasForSale > 0
|
||||
? 'bg-gradient-to-br from-[#ff6600]/15 to-[#ff0040]/10 border-[#ff6600]/40'
|
||||
: 'bg-white/3 border-white/8')}>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Store className="h-4 w-4 text-[#ff6600]" />
|
||||
<span className={cn('font-black text-sm uppercase tracking-wide', hasForSale > 0 ? 'text-[#ff6600]' : 'text-white')}>
|
||||
{hasForSale > 0 ? 'Ready to Sell' : 'Marketplace'}
|
||||
<span className={cn('font-black text-sm uppercase tracking-wide',
|
||||
hasForSale > 0 ? 'text-[#ff6600]' : 'text-gray-300')}>
|
||||
{hasForSale > 0 ? `${hasForSale} ready to sell` : 'Marketplace'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
{hasForSale > 0
|
||||
? <><strong className="text-white">{hasForSale} item{hasForSale > 1 ? 's' : ''}</strong> marked for sale or trade — list on the marketplace in one tap.</>
|
||||
: 'Mark items as "Willing to Sell" or "Willing to Trade" to list them publicly.'}
|
||||
? `${hasForSale} item${hasForSale > 1 ? 's' : ''} marked for sale or trade.`
|
||||
: 'Mark items as "Willing to Sell" or "Willing to Trade" to list them.'}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-xs">
|
||||
<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-cyan-500/50 text-cyan-400 gap-1 h-5"><ArrowRightLeft className="h-2.5 w-2.5" />Trade</Badge>
|
||||
<div className="flex gap-2">
|
||||
<Badge variant="outline" className="border-green-500/40 text-green-400 gap-1 h-5 text-[10px]">
|
||||
<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>
|
||||
<Link to="/marketplace">
|
||||
<Button size="sm"
|
||||
className={cn('w-full gap-2', hasForSale > 0
|
||||
<Button size="sm" className={cn('w-full gap-2 h-8',
|
||||
hasForSale > 0
|
||||
? 'bg-[#ff6600] hover:bg-[#ff6600]/90 text-black font-bold'
|
||||
: 'bg-white/5 hover:bg-white/10 text-white border border-white/10')}>
|
||||
<ShoppingCart className="h-3.5 w-3.5" />
|
||||
Browse Marketplace
|
||||
{hasForSale > 0 && <ChevronRight className="h-3.5 w-3.5 ml-auto" />}
|
||||
: 'bg-white/5 hover:bg-white/10 text-white/70 border border-white/10')}>
|
||||
<ShoppingCart className="h-3.5 w-3.5" />Browse Marketplace
|
||||
</Button>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Wishlist match teaser */}
|
||||
{/* Wishlist */}
|
||||
{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">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Heart className="h-4 w-4 text-cyan-400" />
|
||||
<span className="font-black text-sm text-cyan-400">Wishlist</span>
|
||||
<Badge className="bg-cyan-500/20 text-cyan-400 border-cyan-500/40 text-[10px]">{wishlistItems.length}</Badge>
|
||||
<Heart className="h-3.5 w-3.5 text-cyan-400" />
|
||||
<span className="font-bold text-sm text-cyan-300">Wishlist</span>
|
||||
<Badge className="bg-cyan-500/20 text-cyan-400 text-[10px] h-4 px-1.5">{wishlistItems.length}</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
We'll match your wishlist with friends' stashes and alert you when something fits.
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">We'll match your wishlist with friends' stashes.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Pro tips — compact */}
|
||||
<Card className="bg-black/40 border-white/8">
|
||||
{/* Tips */}
|
||||
<Card className="bg-white/2 border-white/6">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Gem className="h-4 w-4 text-purple-400" />
|
||||
<span className="font-bold text-sm text-purple-300">Tips</span>
|
||||
</div>
|
||||
<ul className="space-y-1.5 text-xs text-muted-foreground">
|
||||
<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 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>
|
||||
<p className="text-[10px] font-black uppercase tracking-widest text-gray-600 mb-2">Tips</p>
|
||||
<ul className="space-y-1.5 text-xs text-gray-500">
|
||||
<li>✏️ Tap to edit any item</li>
|
||||
<li>🏪 Tap to list on marketplace</li>
|
||||
<li>📸 Add photos for better trades</li>
|
||||
<li>🔄 Syncs across all devices</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</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>
|
||||
|
||||
{/* ── 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"
|
||||
style={{ bottom: 'calc(3.5rem + env(safe-area-inset-bottom, 0px) + 0.75rem)', right: '0.75rem' }}>
|
||||
<AddItemDialog
|
||||
onAdd={(item) => addItem.mutate(item)}
|
||||
mode="wtb"
|
||||
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">
|
||||
<Heart className="h-5 w-5 text-black" strokeWidth={2.5} />
|
||||
<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-4.5 w-4.5 text-black" style={{ height: '1.125rem', width: '1.125rem' }} />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
|
@ -1233,8 +1283,9 @@ export default function StashPage() {
|
|||
onAdd={(item) => addItem.mutate(item)}
|
||||
mode="stash"
|
||||
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">
|
||||
<Plus className="h-7 w-7 text-white" strokeWidth={3} />
|
||||
<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"
|
||||
style={{ height: '3.25rem', width: '3.25rem' }}>
|
||||
<Plus className="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue