'use client' import { useEffect, useMemo, useState, useCallback } from 'react' import { useSearchParams } from 'next/navigation' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { Sidebar } from '@/components/Sidebar' import { Loader2, MessageSquare, X, Send, Lock, Target, Gavel, Eye, TrendingUp, Menu, Settings, LogOut, Crown, Zap, Tag, ShoppingCart, DollarSign, CheckCircle, AlertCircle, Mail, ExternalLink, } from 'lucide-react' import Link from 'next/link' import Image from 'next/image' import clsx from 'clsx' // Types type BuyerThread = { id: number listing_id: number domain: string slug: string status: string created_at: string closed_at: string | null closed_reason: string | null } type SellerInquiry = { id: number listing_id: number domain: string slug: string buyer_name: string buyer_email: string offer_amount: number | null status: string created_at: string read_at: string | null replied_at: string | null closed_at: string | null closed_reason: string | null has_unread_reply: boolean last_message_preview: string last_message_at: string last_message_is_buyer: boolean } type Message = { id: number inquiry_id: number listing_id: number sender_user_id: number body: string created_at: string } type InboxTab = 'buying' | 'selling' export default function InboxPage() { const { user, subscription, logout, checkAuth } = useStore() const searchParams = useSearchParams() const openInquiryId = searchParams.get('inquiry') const initialTab = searchParams.get('tab') as InboxTab | null const [activeTab, setActiveTab] = useState(initialTab || 'buying') // Buyer state const [buyerThreads, setBuyerThreads] = useState([]) const [loadingBuyer, setLoadingBuyer] = useState(true) // Seller state const [sellerInquiries, setSellerInquiries] = useState([]) const [loadingSeller, setLoadingSeller] = useState(true) const [sellerUnread, setSellerUnread] = useState(0) // Shared state const [menuOpen, setMenuOpen] = useState(false) const [activeThread, setActiveThread] = useState(null) const [messages, setMessages] = useState([]) const [loadingMessages, setLoadingMessages] = useState(false) const [sending, setSending] = useState(false) const [draft, setDraft] = useState('') const [error, setError] = useState(null) useEffect(() => { checkAuth() }, [checkAuth]) const tierName = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap const isSeller = tierName !== 'Scout' // Scout can't list domains // Load buyer threads const loadBuyerThreads = useCallback(async () => { setLoadingBuyer(true) setError(null) try { const data = await api.getMyInquiryThreads() setBuyerThreads(data) } catch (err: any) { // Silently fail - might not have any threads } finally { setLoadingBuyer(false) } }, []) // Load seller inquiries const loadSellerInquiries = useCallback(async () => { if (!isSeller) { setLoadingSeller(false) return } setLoadingSeller(true) try { const data = await api.getSellerInbox() setSellerInquiries(data.inquiries) setSellerUnread(data.unread) } catch (err: any) { // Silently fail } finally { setLoadingSeller(false) } }, [isSeller]) useEffect(() => { loadBuyerThreads() loadSellerInquiries() // Poll inbox counts every 30 seconds for badge updates const pollInterval = setInterval(() => { loadBuyerThreads() loadSellerInquiries() }, 30000) return () => clearInterval(pollInterval) }, [loadBuyerThreads, loadSellerInquiries]) // Handle URL parameter for opening specific inquiry const threadsById = useMemo(() => { const map = new Map() buyerThreads.forEach(t => map.set(t.id, t)) sellerInquiries.forEach(t => map.set(t.id, t)) return map }, [buyerThreads, sellerInquiries]) useEffect(() => { if (!openInquiryId) return const id = Number(openInquiryId) if (!Number.isFinite(id)) return const t = threadsById.get(id) if (t) { setActiveThread(t) // Determine which tab if ('buyer_name' in t) { setActiveTab('selling') } else { setActiveTab('buying') } } }, [openInquiryId, threadsById]) // Load messages for thread const loadMessages = useCallback(async (thread: BuyerThread | SellerInquiry) => { setLoadingMessages(true) setError(null) try { if ('buyer_name' in thread) { // Seller loading buyer's messages const data = await api.getInquiryMessagesAsSeller(thread.listing_id, thread.id) setMessages(data) } else { // Buyer loading their own messages const data = await api.getInquiryMessagesAsBuyer(thread.id) setMessages(data) } } catch (err: any) { setError(err?.message || 'Failed to load messages') } finally { setLoadingMessages(false) } }, []) useEffect(() => { if (!activeThread) return loadMessages(activeThread) // Poll for new messages every 15 seconds when thread is open const pollInterval = setInterval(() => { loadMessages(activeThread) }, 15000) return () => clearInterval(pollInterval) }, [activeThread, loadMessages]) // Send message const sendMessage = useCallback(async () => { if (!activeThread) return const body = draft.trim() if (!body) return setSending(true) setError(null) try { let created: Message if ('buyer_name' in activeThread) { // Seller replying created = await api.sendInquiryMessageAsSeller(activeThread.listing_id, activeThread.id, body) } else { // Buyer replying created = await api.sendInquiryMessageAsBuyer(activeThread.id, body) } setDraft('') setMessages(prev => [...prev, created]) } catch (err: any) { setError(err?.message || 'Failed to send') } finally { setSending(false) } }, [activeThread, draft]) // Refresh on tab change useEffect(() => { setActiveThread(null) setMessages([]) setDraft('') if (activeTab === 'buying') { loadBuyerThreads() } else { loadSellerInquiries() } }, [activeTab, loadBuyerThreads, loadSellerInquiries]) const isLoading = activeTab === 'buying' ? loadingBuyer : loadingSeller const currentItems = activeTab === 'buying' ? buyerThreads : sellerInquiries const emptyMessage = activeTab === 'buying' ? 'No inquiries yet. Browse Pounce Direct deals and send an inquiry.' : 'No buyer inquiries yet. List a domain for sale to receive offers.' const emptyAction = activeTab === 'buying' ? { href: '/acquire', label: 'Browse Deals' } : { href: '/terminal/listing', label: 'List Domain' } return (
{/* MOBILE HEADER */}
Inbox
{/* DESKTOP HEADER */}
Inbox

Inbox

Manage your domain inquiries and conversations.

{/* TABS */}
{isSeller && ( )}
{/* CONTENT */}
{isLoading ? (
) : error && !activeThread ? (
{error}
) : currentItems.length === 0 ? (

{emptyMessage}

{emptyAction.label}
) : (
{/* Thread/Inquiry list */}
{activeTab === 'buying' ? 'Your Inquiries' : 'Buyer Inquiries'}
{activeTab === 'buying' ? ( // Buyer view buyerThreads.map(t => ( )) ) : ( // Seller view sellerInquiries.map(inq => ( )) )}
{/* Thread detail */}
{!activeThread ? (
Select a conversation
) : ( <> {/* Thread header */}
{activeTab === 'buying' ? 'Thread' : 'Inquiry'}
{'buyer_name' in activeThread ? activeThread.domain : activeThread.domain}
{'buyer_name' in activeThread && (
From: {activeThread.buyer_name} ({activeThread.buyer_email})
)}
{'buyer_name' in activeThread && ( )}
{/* Messages */}
{loadingMessages ? (
) : messages.length === 0 ? (
No messages yet
) : ( messages.map(m => { const isMe = m.sender_user_id === user?.id return (
{isMe ? 'You' : (activeTab === 'buying' ? 'Seller' : 'Buyer')} {new Date(m.created_at).toLocaleString('en-US')}
{m.body}
) }) )}
{/* Reply form */}
{activeThread.status === 'closed' || activeThread.status === 'spam' ? (
This conversation is closed.
) : (