import { useState, useMemo, useRef } from 'react'; import { useSeoMeta } from '@unhead/react'; import { useNavigate, Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogClose, DialogDescription } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Skeleton } from '@/components/ui/skeleton'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { LoginArea } from '@/components/auth/LoginArea'; import { ImageWithFallback } from '@/components/ImageWithFallback'; import { useStash, STASH_CATEGORIES, CONDITION_LABELS, RARITY_TIERS, type StashItem } from '@/hooks/useStash'; import { useStashShowcaseSync } from '@/hooks/useStashShowcase'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useSatsToUsd } from '@/hooks/useExchangeRate'; import { useUploadFile } from '@/hooks/useUploadFile'; import { Package, Plus, Trash2, Edit3, Lock, Users, Globe, Zap, ArrowRightLeft, Shield, Star, Search, AlertTriangle, ShoppingCart, Heart, Backpack, Trophy, X, ImagePlus, Loader2, Store, MapPin, LayoutGrid, List, } from 'lucide-react'; import { cn } from '@/lib/utils'; // ─── Rarity helpers ────────────────────────────────────────────────────────── const RARITY_COLORS = { common: 'from-gray-500 to-gray-600', uncommon: 'from-green-500 to-emerald-600', rare: 'from-blue-500 to-indigo-600', epic: 'from-purple-500 to-violet-600', legendary: 'from-yellow-400 to-orange-500', }; const RARITY_GLOW = { common: '', uncommon: 'shadow-[0_0_15px_rgba(34,197,94,0.3)]', rare: 'shadow-[0_0_20px_rgba(59,130,246,0.4)]', epic: 'shadow-[0_0_25px_rgba(168,85,247,0.5)]', legendary: 'shadow-[0_0_30px_rgba(250,204,21,0.6)] animate-pulse', }; const RARITY_BORDER = { common: 'border-gray-500/40', uncommon: 'border-green-500/50', rare: 'border-blue-500/50', epic: 'border-purple-500/60', legendary: 'border-yellow-400/70', }; // ─── List Row — mobile default ──────────────────────────────────────────────── function StashListRow({ item, onEdit, onDelete, onListOnMarketplace, isWTB = false }: { item: StashItem; onEdit: () => void; onDelete: () => void; onListOnMarketplace?: () => void; isWTB?: boolean; }) { const category = STASH_CATEGORIES[item.category]; const condition = CONDITION_LABELS[item.condition]; const rarity = (item.rarity || category.rarity) as keyof typeof RARITY_COLORS; const rarityTier = RARITY_TIERS[rarity]; const usdValue = useSatsToUsd(item.minPrice || item.maxPrice || 0); const canListOnMarketplace = !isWTB && (item.willingToSell || item.willingToTrade); return (
{item.images?.[0] ? {category.emoji}} /> : {category.emoji}}
{isWTB && WTB}

{item.title}

{item.brand && {item.brand}} {item.size && • {item.size}} {!isWTB && item.storageLocation && item.storageLocation !== 'other' && ( • {{ home:'🏠',car:'🚗',skatepark:'🛹',warehouse:'🏭',storage_unit:'🔒',van:'🚐',friends:'👫',parents:'👨‍👩‍👦',wardrobe:'🚪',backpack:'🎒',sponsor:'🤝',other:'📦' }[item.storageLocation]} )}
{!isWTB && (
{Array.from({ length: 5 }).map((_, i) => ( ))}
)} {(item.minPrice || item.maxPrice) && ( {(item.maxPrice || item.minPrice || 0).toLocaleString()} {usdValue && (~${usdValue.toFixed(0)})} )}
{item.visibility === 'private' && } {item.visibility === 'friends' && } {item.visibility === 'public' && }
{item.willingToTrade && ↔} {item.willingToSell && !isWTB && ⚡}
{canListOnMarketplace && onListOnMarketplace && ( )}
); } // ─── Grid Card — desktop default ────────────────────────────────────────────── function StashItemCard({ item, onEdit, onDelete, onListOnMarketplace, isWTB = false }: { item: StashItem; onEdit: () => void; onDelete: () => void; onListOnMarketplace?: () => void; isWTB?: boolean; }) { const category = STASH_CATEGORIES[item.category]; const condition = CONDITION_LABELS[item.condition]; const rarity = (item.rarity || category.rarity) as keyof typeof RARITY_COLORS; const usdValue = useSatsToUsd(item.minPrice || item.maxPrice || 0); const rarityTier = RARITY_TIERS[rarity]; const canListOnMarketplace = !isWTB && (item.willingToSell || item.willingToTrade); return (
{rarity !== 'common' &&
}
{isWTB && (
Wishlist
)}
{rarityTier.label}
{item.images?.[0] ? ( ) : (
{category.emoji} {category.label}
)}
{canListOnMarketplace && onListOnMarketplace && ( )}

{item.title}

{category.emoji} {item.brand && {item.brand}} {item.size && • {item.size}}

{!isWTB && item.storageLocation && item.storageLocation !== 'other' && ( {{ home:'🏠 Home',car:'🚗 Car',skatepark:'🛹 Skatepark',warehouse:'🏭 Warehouse',storage_unit:'🔒 Storage Unit',van:'🚐 Van',friends:"👫 Friend's",parents:"👨‍👩‍👦 Parents'",wardrobe:'🚪 Wardrobe',backpack:'🎒 Backpack',sponsor:'🤝 Sponsor',other:'📦 Other' }[item.storageLocation]} )} {!isWTB && (
{Array.from({ length: 5 }).map((_, i) => ( ))} {condition.label.split(' ')[1]}
)}
{(item.minPrice || item.maxPrice) ? (
{isWTB ? 'Up to ' : ''}{(item.maxPrice || item.minPrice || 0).toLocaleString()} sats {usdValue && (~${usdValue.toFixed(0)})}
) : ( No price set )}
{item.visibility === 'private' && Private} {item.visibility === 'friends' && Friends only} {item.willingToTrade && } {item.willingToSell && !isWTB && }
); } // ─── Add Item Dialog ────────────────────────────────────────────────────────── function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit) => void; mode?: 'stash' | 'wtb'; trigger?: React.ReactNode }) { const [open, setOpen] = useState(false); const isWTB = mode === 'wtb'; const fileInputRef = useRef(null); const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile(); const [formData, setFormData] = useState>({ category: 'deck', condition: 'good', visibility: 'private', willingToSell: !isWTB, willingToTrade: false, itemType: isWTB ? 'wtb' : 'stash', images: [] }); const handleImageUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; try { const newImages: string[] = [...(formData.images || [])]; for (const file of Array.from(files)) { const tags = await uploadFile(file); const urlTag = tags.find(t => t[0] === 'url'); if (urlTag) newImages.push(urlTag[1]); } setFormData({ ...formData, images: newImages }); } catch { /* ignore */ } if (fileInputRef.current) fileInputRef.current.value = ''; }; const removeImage = (index: number) => { const imgs = [...(formData.images || [])]; imgs.splice(index, 1); setFormData({ ...formData, images: imgs }); }; const handleSubmit = () => { if (!formData.title || !formData.category) return; if (!isWTB && !formData.condition) return; onAdd({ title: formData.title, category: formData.category as StashItem['category'], condition: isWTB ? 'good' : (formData.condition as StashItem['condition']), visibility: formData.visibility || 'private', size: formData.size, brand: formData.brand, description: formData.description, minPrice: isWTB ? undefined : formData.minPrice, maxPrice: isWTB ? formData.maxPrice : undefined, willingToSell: isWTB ? false : formData.willingToSell, willingToTrade: formData.willingToTrade, images: formData.images, itemType: isWTB ? 'wtb' : 'stash', storageLocation: isWTB ? undefined : formData.storageLocation, }); setFormData({ category: 'deck', condition: 'good', visibility: 'private', willingToSell: !isWTB, willingToTrade: false, itemType: isWTB ? 'wtb' : 'stash', images: [] }); setOpen(false); }; const defaultTrigger = isWTB ? ( ) : ( ); return ( {trigger ?? defaultTrigger} {isWTB ? <>Add to Wishlist : <>Add Item to Stash} {isWTB ? "Add items you're looking for." : "Add gear to your encrypted private inventory."}
setFormData({ ...formData, title: e.target.value })} className="bg-black/50 border-white/20" />
setFormData({ ...formData, brand: e.target.value })} className="bg-black/50 border-white/20" />
setFormData({ ...formData, size: e.target.value })} className="bg-black/50 border-white/20" />