"use client" import { useState, useEffect } from "react" import Link from "next/link" import { usePathname } from "next/navigation" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Cpu, Heart, MessageCircle, Share, Search, Monitor, Users, Eye, BarChart3, ArrowLeft, Plus, X, TrendingUp, TrendingDown, DollarSign, Zap, MemoryStick, HardDrive, Fan, Settings, Calendar } from "lucide-react" import { buildService, buildComponentService } from "@/lib/database" import { formatCurrency, PRICE_RANGES } from "@/lib/currency" import { useAuth } from "@/contexts/supabase-auth-context" import { supabase } from "@/lib/supabase" import { swal } from "@/lib/sweetalert" interface BuildWithDetails { build_id: number build_name: string date_created: string user_id: number build_type_id: number users: { user_id: number user_name: string email: string avatar_url: string } build_types: { build_type_id: number type_name: string } components?: any[] totalPrice?: number comments?: number views?: number description?: string } export default function BuildsPage() { const { user } = useAuth() const pathname = usePathname() const [builds, setBuilds] = useState([]) const [statistics, setStatistics] = useState({ totalBuilders: 0, totalBuilds: 0, totalLikes: 0 }) const [searchTerm, setSearchTerm] = useState("") const [sortBy, setSortBy] = useState("popular") const [priceFilter, setPriceFilter] = useState("all") const [typeFilter, setTypeFilter] = useState("all") const [buildsLimit, setBuildsLimit] = useState(30) const [likedBuilds, setLikedBuilds] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [activeTab, setActiveTab] = useState<"browse" | "compare">("browse") const [selectedBuildsForCompare, setSelectedBuildsForCompare] = useState([]) const [compareSearchTerm, setCompareSearchTerm] = useState("") const [compareActiveTab, setCompareActiveTab] = useState("overview") const [sharePopupOpen, setSharePopupOpen] = useState(false) const [shareUrl, setShareUrl] = useState("") // Subscriptions: set up once per pathname so "Load more" (buildsLimit) doesn't tear them down useEffect(() => { const buildsChannel = supabase .channel("builds-changes") .on("postgres_changes", { event: "*", schema: "public", table: "builds" }, () => { fetchBuilds() fetchStatistics() }) .subscribe() const likesChannel = supabase .channel("likes-changes") .on("postgres_changes", { event: "*", schema: "public", table: "build_likes" }, () => { fetchBuilds() fetchStatistics() }) .subscribe() return () => { supabase.removeChannel(buildsChannel) supabase.removeChannel(likesChannel) } }, [pathname]) // Initial fetch and refetch when limit changes (Load more) useEffect(() => { fetchBuilds() fetchStatistics() }, [pathname, buildsLimit]) // Fetch user's liked builds useEffect(() => { if (!user) return; const fetchLiked = async () => { const { data, error } = await supabase .from("build_likes") .select("build_id") .eq("user_id", user.user_id) if (!error && data) setLikedBuilds(data.map((r) => r.build_id)) } fetchLiked() }, [user]) const fetchBuilds = async () => { setLoading(true) try { // 1️⃣ Fetch builds with user info and components const { data: buildsData, error: buildsError } = await supabase .from("builds") .select(` *, users(user_name, user_id, avatar_url), build_types(build_type_id, type_name), build_components( components( component_name, component_categories(category_name) ) ) `) .order("date_created", { ascending: false }) .limit(buildsLimit) if (buildsError) throw buildsError if (!buildsData || buildsData.length === 0) { setBuilds([]) setLoading(false) return } const buildIds = buildsData.map(b => b.build_id) // 2️⃣ Fetch likes const { data: likesData } = await supabase .from("build_likes") .select("build_id") .in("build_id", buildIds) // 3️⃣ Fetch comments count const { data: commentsData } = await supabase .from("build_comments") .select("build_id") .in("build_id", buildIds) // 4️⃣ Fetch views count from build_views table const { data: viewsData } = await supabase .from("build_views") .select("build_id") .in("build_id", buildIds) // 5️⃣ Reduce counts const likeCountMap: Record = {} likesData?.forEach(like => { likeCountMap[like.build_id] = (likeCountMap[like.build_id] || 0) + 1 }) const commentCountMap: Record = {} commentsData?.forEach(comment => { commentCountMap[comment.build_id] = (commentCountMap[comment.build_id] || 0) + 1 }) const viewsCountMap: Record = {} viewsData?.forEach(view => { viewsCountMap[view.build_id] = (viewsCountMap[view.build_id] || 0) + 1 }) // 6️⃣ Merge counts into builds const enrichedBuilds = buildsData.map(build => ({ ...build, totalPrice: build.total_price, likes: likeCountMap[build.build_id] || 0, comments: commentCountMap[build.build_id] || 0, views: viewsCountMap[build.build_id] || 0, components: build.build_components?.map(bc => ({ ...bc.components, // flatten components category_name: bc.components?.component_categories?.category_name })) || [] })) setBuilds(enrichedBuilds) } catch (err: any) { console.error(err.message || "Unknown error") setError("Failed to fetch builds. Please try again.") } finally { setLoading(false) } } const fetchStatistics = async () => { try { const stats = await buildService.getStatistics(); setStatistics(stats) } catch (err) { console.error(err) } } const toggleLike = async (buildId: number) => { if (!user?.user_id) return const build = builds.find(b => b.build_id === buildId) const buildOwnerId = build?.user_id if (buildOwnerId === user.user_id) { swal.info("Oops!", "You cannot like your own build.") return } const alreadyLiked = likedBuilds.includes(buildId) if (alreadyLiked) { // UNLIKE await supabase .from("build_likes") .delete() .eq("build_id", buildId) .eq("user_id", user.user_id) setLikedBuilds(prev => prev.filter(id => id !== buildId)) setBuilds(prev => prev.map(b => b.build_id === buildId ? { ...b, likes: Math.max(b.likes - 1, 0) } : b ) ) // Remove notification if (buildOwnerId && buildOwnerId !== user.user_id) { await supabase .from("notifications") .delete() .eq("user_id", buildOwnerId) .eq("actor_user_id", user.user_id) .eq("type", "like") .eq("build_id", buildId) } } else { // LIKE await supabase .from("build_likes") .insert({ build_id: buildId, user_id: user.user_id, created_at: new Date().toISOString() }) setLikedBuilds(prev => [...prev, buildId]) setBuilds(prev => prev.map(b => b.build_id === buildId ? { ...b, likes: (b.likes || 0) + 1 } : b ) ) // Insert notification if (buildOwnerId && buildOwnerId !== user.user_id) { await supabase .from("notifications") .insert({ user_id: buildOwnerId, actor_user_id: user.user_id, type: "like", build_id: buildId, created_at: new Date().toISOString(), }) // Send push notification to the build owner (non-blocking) try { const payload = { userId: buildOwnerId, notificationType: 'buildLike', title: `${user.user_name} liked your build`, body: `${user.user_name} liked ${build.build_name}`, url: `/builds/${buildId}`, }; console.log('Sending push notification payload (builds list):', payload); fetch('/api/notifications/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) .then(res => res.json().then(r => console.log('Push send result (builds list):', r)).catch(() => {})) .catch(err => console.error('Error sending push (builds list):', err)); } catch (err) { console.error('Error preparing push send (builds list):', err); } } } } const getFilteredBuilds = (): BuildWithDetails[] => { let filtered = builds.filter( b => b.build_name.toLowerCase().includes(searchTerm.toLowerCase()) || b.build_types?.type_name?.toLowerCase().includes(searchTerm.toLowerCase()) || (b.users?.user_name && b.users.user_name.toLowerCase().includes(searchTerm.toLowerCase())) ) if (priceFilter !== "all") { filtered = filtered.filter(build => { const price = build.totalPrice || 0 switch (priceFilter) { case "budget": return price < PRICE_RANGES.budget.max case "mid": return price >= PRICE_RANGES.mid.min && price < PRICE_RANGES.mid.max case "high": return price >= PRICE_RANGES.high.min default: return true } }) } if (typeFilter !== "all") { filtered = filtered.filter(b => (b.build_types?.type_name || "").toLowerCase() === typeFilter.toLowerCase()) } switch (sortBy) { case "recent": return [...filtered].sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime()) case "price-low": return [...filtered].sort((a, b) => (a.totalPrice || 0) - (b.totalPrice || 0)) case "price-high": return [...filtered].sort((a, b) => (b.totalPrice || 0) - (a.totalPrice || 0)) case "likes": return [...filtered].sort((a, b) => (b.likes || 0) - (a.likes || 0)) default: return [...filtered].sort((a, b) => (b.likes || 0) - (a.likes || 0)) } } const filteredBuilds = getFilteredBuilds() const formatNumber = (num: number) => num.toLocaleString() if (loading && builds.length === 0) { return (

Loading builds...

) } const buildTypeOptions = ["all", ...Array.from(new Set(builds.map(b => b.build_types?.type_name).filter(Boolean) as string[]))] return (
{error && (

{error}

)} {/* Page header — Community Builds */}

Community Builds

Discover builds from other builders. Get inspiration, compare specs, and clone builds to customize in the PC Builder.

{/* Stats strip — card style */}

{formatNumber(statistics.totalBuilders)}

Builders

{formatNumber(statistics.totalBuilds)}

Builds

{formatNumber(statistics.totalLikes)}

Likes

setActiveTab(v as "browse" | "compare")} className="mb-8"> Browse Builds Compare Builds {/* Browse Tab */}

Search & filters

setSearchTerm(e.target.value)} />
{filteredBuilds.map((build) => { const creator = build.users const isLiked = likedBuilds.includes(build.build_id) const profileHref = user && creator?.user_id === user.user_id ? `/profile` : `/profile/${creator?.user_id}`; const cpuComponent = build.components?.find( (c: any) => c.category_name === 'CPU' ) const gpuComponent = build.components?.find( (c: any) => c.category_name === 'GPU' ) const isPopular = (build.likes || 0) >= 5 const componentCount = build.components?.length || 0 return (
{build.build_name} {isPopular && ( Popular )}
{creator?.avatar_url ? ( ) : ( {creator?.user_name ? creator.user_name.charAt(0).toUpperCase() : "?"} )} {creator?.user_name || "Unknown"}
{formatCurrency(build.totalPrice || 0)} {componentCount} part{componentCount !== 1 ? "s" : ""}
{(build.views || 0).toLocaleString()}

{build?.description || "PC build shared by the community."}

{build.build_types?.type_name || "Build"}
{cpuComponent && (
{cpuComponent?.component_name}
)} {gpuComponent && (
{gpuComponent?.component_name}
)}
) })}
{filteredBuilds.length === 0 && !loading && (

No builds match your filters

Try different search terms, clear the type or price filter, or be the first to share a build.

)} {filteredBuilds.length > 0 && builds.length >= buildsLimit && (
)}
{/* Compare Tab */} {selectedBuildsForCompare.length === 0 ? ( Select Builds to Compare

Choose builds from the Browse tab to compare them side by side

) : (
Comparing {selectedBuildsForCompare.length} Builds

Analyze differences in specs, performance, and cost

{selectedBuildsForCompare.map((build, index) => (
Build {index + 1} {build.build_name}
))}
Overview Components Price Specs
{selectedBuildsForCompare.map((build, index) => ( {build.build_name}

{build.description || 'PC Build'}

Total Price {formatCurrency(build.totalPrice || 0)}
Likes {build.likes || 0}
Views {(build.views || 0).toLocaleString()}
Components {build.components?.length || 0}
))}
Price Comparison
{selectedBuildsForCompare.map((build, index) => { const maxPrice = Math.max(...selectedBuildsForCompare.map(b => b.totalPrice || 0)) const pricePercentage = maxPrice > 0 ? ((build.totalPrice || 0) / maxPrice) * 100 : 0 return (
{build.build_name} {formatCurrency(build.totalPrice || 0)}
) })}
Component Comparison
{selectedBuildsForCompare.map((build, index) => ( ))} {[ { display: 'CPU', dbNames: ['CPU'] }, { display: 'GPU', dbNames: ['GPU'] }, { display: 'Memory', dbNames: ['Memory', 'RAM'] }, { display: 'Storage', dbNames: ['Storage'] } ].map(({ display, dbNames }) => ( {selectedBuildsForCompare.map((build) => { const component = build.components?.find((bc: any) => { const categoryName = bc.components?.component_categories?.category_name return categoryName && dbNames.includes(categoryName) }) return ( ) })} ))}
Component Build {index + 1}
{display} {component?.components?.component_name || 'N/A'}
Detailed Specifications
{selectedBuildsForCompare.map((build, index) => ( {build.build_name}
Price: {formatCurrency(build.totalPrice || 0)}
Components: {build.components?.length || 0}
Type: {build.build_types?.type_name || 'N/A'}
Created: {new Date(build.date_created).toLocaleDateString()}
))}
)}
{/* Share Popup */} {sharePopupOpen && (

Share build

Copy the link to share this build with others.

)}
) }