'use client'; import React, { useState, useEffect } from 'react'; import Link from 'next/link'; import Header from '@/components/Header'; import Footer from '@/components/Footer'; import AdImage from '@/components/AdImage'; import { ADS_728X90, shuffle } from '@/lib/ads'; import { createClient } from '@/lib/supabase/client'; interface ForumCategory { id: string; slug: string; name: string; description: string; icon: string; thread_count: number; post_count: number; display_order: number; } interface ForumThread { id: string; title: string; author_name: string; reply_count: number; view_count: number; last_reply_at: string; created_at: string; upvote_count?: number; forum_categories?: { name: string; slug: string }; } type SortOption = 'newest' | 'top-voted' | 'unanswered'; type TopMetric = 'upvotes' | 'replies' | 'views'; type FeedMode = 'discover' | 'following'; 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({ limit: '5', sort: sortMap[activeMetric] }); if (categoryId) params.set('category_id', categoryId); 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.upvote_count ?? 0, label: 'votes', color: 'text-[#22c55e]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#3b82f6]' }; 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}

{thread.author_name} {thread.forum_categories && ( <> · {thread.forum_categories.name} )} {' · '}{timeAgo(thread.created_at)}

{/* Metric value */}
{metric.value.toLocaleString()}
{metric.label}
); })}
); } export default function ForumIndexPage() { const [categories, setCategories] = useState([]); const [threads, setThreads] = useState([]); const [loading, setLoading] = useState(true); const [threadsLoading, setThreadsLoading] = useState(false); const [error, setError] = useState(null); const [search, setSearch] = useState(''); const [selectedCategory, setSelectedCategory] = useState(''); const [sort, setSort] = useState('newest'); // Feed mode: discover (default) or following const [feedMode, setFeedMode] = useState('discover'); const [followingThreads, setFollowingThreads] = useState([]); const [followingLoading, setFollowingLoading] = useState(false); const [currentUserId, setCurrentUserId] = useState(null); // Debounced search value const [debouncedSearch, setDebouncedSearch] = useState(''); useEffect(() => { const t = setTimeout(() => setDebouncedSearch(search), 350); return () => clearTimeout(t); }, [search]); // Get current user useEffect(() => { const supabase = createClient(); supabase.auth.getUser().then(({ data: { user } }) => { setCurrentUserId(user?.id || null); }); }, []); // Load categories once useEffect(() => { fetch('/api/forum/categories') .then(r => r.json()) .then(d => { setCategories(d.categories || []); setLoading(false); }) .catch(() => { setError('Failed to load forum categories.'); setLoading(false); }); }, []); // Load following feed when tab is active useEffect(() => { if (feedMode !== 'following' || !currentUserId) return; setFollowingLoading(true); fetch('/api/forum/following-feed?limit=30') .then(r => r.json()) .then(d => { setFollowingThreads(d.threads || []); setFollowingLoading(false); }) .catch(() => setFollowingLoading(false)); }, [feedMode, currentUserId]); // Load threads when search/category/sort changes const isSearchMode = debouncedSearch.trim() !== '' || selectedCategory !== '' || sort !== 'newest'; useEffect(() => { if (!isSearchMode) { setThreads([]); return; } setThreadsLoading(true); const params = new URLSearchParams({ limit: '30', sort }); if (debouncedSearch.trim()) params.set('search', debouncedSearch.trim()); if (selectedCategory) params.set('category_id', selectedCategory); fetch(`/api/forum/threads?${params.toString()}`) .then(r => r.json()) .then(d => { setThreads(d.threads || []); setThreadsLoading(false); }) .catch(() => setThreadsLoading(false)); }, [debouncedSearch, selectedCategory, sort, isSearchMode]); const sortOptions: { value: SortOption; label: string }[] = [ { value: 'newest', label: 'Newest' }, { value: 'top-voted', label: 'Top Voted' }, { value: 'unanswered', label: 'Unanswered' }, ]; return ( <>
{/* Hero */}
The Crew Lounge

The Crew Lounge

The Crew Lounge: connect with broadcast, motion picture and post production professionals worldwide. Ask questions, share expertise, and discuss the technology shaping the industry.

{/* Feed Mode Tabs */}
{/* Following Feed */} {feedMode === 'following' && (
{!currentUserId && (

Sign in to see threads from people you follow.

Sign In
)} {currentUserId && followingLoading && (
{[...Array(6)].map((_, i) => (
))}
)} {currentUserId && !followingLoading && followingThreads.length === 0 && (
👥

Your following feed is empty

Follow other members to see their threads here.

setFeedMode('discover')} className="inline-block bg-[#3b82f6] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#2563eb] transition-colors" > Discover Members
)} {currentUserId && !followingLoading && followingThreads.length > 0 && (

{followingThreads.length} thread{followingThreads.length !== 1 ? 's' : ''} from people you follow

{followingThreads.map(thread => (

{thread.title}

by{' '} e.stopPropagation()} className="text-[#3b82f6] hover:underline" > {thread.author_name} {thread.forum_categories && ( <> · {thread.forum_categories.name} )} {' · '}{timeAgo(thread.created_at)}

{thread.reply_count}
replies
{thread.view_count}
views
))}
)}
)} {/* Discover Feed */} {feedMode === 'discover' && ( <> {/* Search + Filter + Sort Bar */}
{/* Search */}
setSearch(e.target.value)} className="w-full bg-[#1a1a1a] border border-[#2a2a2a] focus:border-[#3b82f6] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors" /> {search && ( )}
{/* Category Filter */}
{/* Sort Options */}
{sortOptions.map(opt => ( ))}
{/* Thread Results (when searching/filtering/sorting) */} {isSearchMode && (
{threadsLoading && (
{[...Array(6)].map((_, i) => (
))}
)} {!threadsLoading && threads.length === 0 && (

No threads found

Try adjusting your search or filters.

)} {!threadsLoading && threads.length > 0 && (

{threads.length} thread{threads.length !== 1 ? 's' : ''} found

{threads.map(thread => (

{thread.title}

by {thread.author_name} {thread.forum_categories && ( <> · {thread.forum_categories.name} )} {' · '}{timeAgo(thread.created_at)}

{thread.reply_count}
replies
{sort === 'top-voted' && (
+{thread.upvote_count ?? 0}
votes
)} ))}
)}
)} {/* Default view: Top Threads + Category Grid */} {!isSearchMode && ( <> {/* Top Performing Threads Widget */} {!loading && } {loading && (
{[...Array(8)].map((_, i) => (
))}
)} {error && (
{error}
)} {!loading && !error && ( <>

Browse Categories

{categories.map(cat => ( {cat.icon}

{cat.name}

{cat.description}

{cat.thread_count}
threads
{cat.post_count}
posts
))}
)} )} )} {/* Forum guidelines */}

Community Guidelines

  • • Be respectful and professional — this is a community of peers
  • • Share real-world experience and cite sources when possible
  • • The Broadcast Beat AI assistant is available in threads to help answer questions — it is clearly labeled
  • • No spam, self-promotion, or vendor pitches without disclosure
{/* In-content 728x90 banner */} {ADS_728X90.length > 0 && (
)}