'use client'; import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import Header from '@/components/Header'; import Footer from '@/components/Footer'; import ImageAttachButton from '@/components/forum/ImageAttachButton'; import { createClient } from '@/lib/supabase/client'; interface ForumCategory { id: string; slug: string; name: string; description: string; icon: string; thread_count: number; } interface ForumThread { id: string; title: string; body: string; author_name: string; status: string; reply_count: number; view_count: number; upvotes?: number; last_reply_at: string; created_at: string; is_ai_seeded: boolean; } type SortOption = 'newest' | 'top-voted' | 'unanswered'; type TopMetric = 'upvotes' | 'replies' | 'views'; function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; const days = Math.floor(hrs / 24); if (days < 30) return `${days}d ago`; return new Date(dateStr).toLocaleDateString(); } function TopThreadsWidget({ categoryId }: { categoryId: string }) { const [activeMetric, setActiveMetric] = useState('upvotes'); const [threads, setThreads] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); const sortMap: Record = { upvotes: 'top-voted', replies: 'most-replied', views: 'most-viewed', }; const params = new URLSearchParams({ category_id: categoryId, limit: '5', sort: sortMap[activeMetric] }); fetch(`/api/forum/threads?${params.toString()}`) .then(r => r.json()) .then(d => { setThreads(d.threads || []); setLoading(false); }) .catch(() => setLoading(false)); }, [activeMetric, categoryId]); const metrics: { value: TopMetric; label: string; icon: string }[] = [ { value: 'upvotes', label: 'Top Voted', icon: 'โ–ฒ' }, { value: 'replies', label: 'Most Replies', icon: '๐Ÿ’ฌ' }, { value: 'views', label: 'Most Viewed', icon: '๐Ÿ‘' }, ]; const getMetricValue = (thread: ForumThread) => { if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#1D4ED8]' }; return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' }; }; return (
{/* Header */}
๐Ÿ†

Top Performing Threads

{/* Metric Tabs */}
{metrics.map(m => ( ))}
{/* Thread List */}
{loading && ( <> {[...Array(5)].map((_, i) => (
))} )} {!loading && threads.length === 0 && (
No threads yet. Be the first to start a discussion!
)} {!loading && threads.map((thread, idx) => { const metric = getMetricValue(thread); return ( {/* Rank */} {idx + 1} {/* Thread info */}

{thread.title}

e.stopPropagation()}>{thread.author_name} {' ยท '}{timeAgo(thread.created_at)}

{/* Metric value */}
{metric.value.toLocaleString()}
{metric.label}
); })}
); } export default function ForumCategoryPage() { const params = useParams(); const slug = params?.slug as string; const [category, setCategory] = useState(null); const [threads, setThreads] = useState([]); const [loading, setLoading] = useState(true); const [threadsLoading, setThreadsLoading] = useState(false); const [error, setError] = useState(null); const [showNewThread, setShowNewThread] = useState(false); const [newTitle, setNewTitle] = useState(''); const [newBody, setNewBody] = useState(''); const [submitting, setSubmitting] = useState(false); const [newError, setNewError] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); useEffect(() => { const supabase = createClient(); supabase.auth.getUser().then(({ data: { user } }) => setIsAuthenticated(!!user)); }, []); const [search, setSearch] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [sort, setSort] = useState('newest'); useEffect(() => { const t = setTimeout(() => setDebouncedSearch(search), 350); return () => clearTimeout(t); }, [search]); // Load category info once useEffect(() => { if (!slug) return; fetch('/api/forum/categories') .then(r => r.json()) .then(d => { const cat = (d.categories || []).find((c: ForumCategory) => c.slug === slug); setCategory(cat || null); setLoading(false); }) .catch(() => { setError('Failed to load category.'); setLoading(false); }); }, [slug]); // Load threads when category/search/sort changes const fetchThreads = useCallback(() => { if (!category) return; setThreadsLoading(true); const qParams = new URLSearchParams({ category_id: category.id, limit: '50', sort }); if (debouncedSearch.trim()) qParams.set('search', debouncedSearch.trim()); fetch(`/api/forum/threads?${qParams.toString()}`) .then(r => r.json()) .then(d => { setThreads(d?.threads || []); setThreadsLoading(false); }) .catch(() => { setError('Failed to load threads.'); setThreadsLoading(false); }); }, [category, debouncedSearch, sort]); useEffect(() => { fetchThreads(); }, [fetchThreads]); const handleNewThread = async (e: React.FormEvent) => { e.preventDefault(); if (!newTitle.trim() || !newBody.trim() || !category) return; setSubmitting(true); setNewError(null); try { const res = await fetch('/api/forum/threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ category_id: category.id, title: newTitle, body: newBody, }), }); const data = await res.json(); if (!res.ok) { if (res.status === 401) { window.location.href = `/forum/login?next=${encodeURIComponent(window.location.pathname)}`; return; } throw new Error(data.error || `Failed (${res.status})`); } if (data.thread) { setThreads(prev => [data.thread, ...prev]); setNewTitle(''); setNewBody(''); setShowNewThread(false); } } catch (err: any) { setNewError(err.message || 'Failed to create thread. Please try again.'); } finally { setSubmitting(false); } }; const sortOptions: { value: SortOption; label: string }[] = [ { value: 'newest', label: 'Newest' }, { value: 'top-voted', label: 'Top Voted' }, { value: 'unanswered', label: 'Unanswered' }, ]; return ( <>
{/* Breadcrumb + Header */}
{category?.icon || '๐Ÿ’ฌ'}

{category?.name || 'Loading...'}

{category && (

{category.thread_count} threads

)}
{isAuthenticated ? ( ) : ( Sign in to post )}
{/* New Thread Form โ€” auth-gated */} {showNewThread && isAuthenticated && (

Start a New Thread

setNewTitle(e.target.value)} required className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-[#0F172A] font-body text-sm placeholder-[#888] focus:outline-none focus:border-[#1D4ED8]" />