"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 { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Cpu, Heart, MessageCircle, Share, Search, Monitor, Users, Eye, BarChart3, ShoppingCart, Calendar, Trash2, Pencil } 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 likes?: number comments?: number views?: number description?: string } export default function BuildsPage() { const { user, isLoading: authLoading } = 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 [likedBuilds, setLikedBuilds] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [likeOwnPopupOpen, setLikeOwnPopupOpen] = useState(false) const [sharePopupOpen, setSharePopupOpen] = useState(false) const [shareUrl, setShareUrl] = useState("") const [deletePopupOpen, setDeletePopupOpen] = useState(false) const [buildToDelete, setBuildToDelete] = useState(null) const [loadingTimeout, setLoadingTimeout] = useState(false) // Safety: stop showing loading after 12s so the page never stays stuck useEffect(() => { if (!authLoading && !user) return if (!loading && builds.length > 0) return const t = setTimeout(() => setLoadingTimeout(true), 12000) return () => clearTimeout(t) }, [authLoading, user, loading, builds.length]) // Fetch builds and statistics (only when user is available) useEffect(() => { if (!user) { setLoading(false) setLoadingTimeout(false) return } setLoadingTimeout(false) fetchBuilds() fetchStatistics() const buildsChannel = supabase .channel('builds-changes') .on('postgres_changes', { event: '*', schema: 'public', table: 'builds' }, () => { fetchBuilds() fetchStatistics() }) .subscribe() return () => { supabase.removeChannel(buildsChannel) } }, [pathname, user]) // 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 () => { if (!user) return; try { setLoading(true); setError(null); const { data: userBuilds, error } = await supabase .from("builds") .select(` build_id, build_name, date_created, user_id, build_type_id, total_price, description, users:user_id ( user_id, user_name, email, avatar_url ), build_types:build_type_id ( build_type_id, type_name ) `) .eq("user_id", user.user_id); if (error) throw error; const list = userBuilds ?? []; if (list.length === 0) { setBuilds([]); return; } const buildIds = list.map((b) => b.build_id); // Batch fetch likes, comments, views (3 queries total instead of 3 per build) const [likesRes, commentsRes, viewsRes] = await Promise.all([ supabase.from("build_likes").select("build_id").in("build_id", buildIds), supabase.from("build_comments").select("build_id").in("build_id", buildIds), supabase.from("build_views").select("build_id").in("build_id", buildIds), ]); const countByBuild = (rows: { build_id: number }[] | null) => { const map: Record = {}; buildIds.forEach((id) => (map[id] = 0)); (rows ?? []).forEach((r) => (map[r.build_id] = (map[r.build_id] ?? 0) + 1)); return map; }; const likesMap = countByBuild(likesRes.data); const commentsMap = countByBuild(commentsRes.data); const viewsMap = countByBuild(viewsRes.data); const buildsWithDetails = list.map((build) => ({ ...build, totalPrice: Number(build.total_price || 0), likes: likesMap[build.build_id] ?? 0, comments: commentsMap[build.build_id] ?? 0, views: viewsMap[build.build_id] ?? 0, })); setBuilds(buildsWithDetails); } catch (err: any) { console.error(err); setError(err.message || "Failed to load builds"); } finally { setLoading(false); } }; const fetchStatistics = async () => { try { if (!user) return; // 1️⃣ Get all build IDs owned by the user const { data: userBuilds, error: buildsError } = await supabase .from("builds") .select("build_id") .eq("user_id", user.user_id); if (buildsError) throw buildsError; const buildIds = userBuilds.map(b => b.build_id); // 2️⃣ Count total builds const totalBuilds = buildIds.length; if (buildIds.length === 0) { setStatistics({ totalBuilders: 1, totalBuilds: 0, totalLikes: 0, }); return; } // 3️⃣ Count likes from build_likes (THIS IS THE FIX) const { count: totalLikes, error: likesError } = await supabase .from("build_likes") .select("*", { count: "exact", head: true }) .in("build_id", buildIds); if (likesError) throw likesError; // 4️⃣ Keep existing builder stats const stats = await buildService.getStatistics(); setStatistics({ ...stats, totalBuilds, totalLikes: totalLikes || 0, }); } catch (err) { console.error("Error fetching statistics:", err); } }; const handleLike = async (build: BuildWithDetails) => { if (!user) return; const isLiked = likedBuilds.includes(build.build_id); // ❌ Prevent liking your own build if (build.users?.user_id === user.user_id) { setLikeOwnPopupOpen(true) return } if (isLiked) { const { error } = await supabase .from("build_likes") .delete() .eq("build_id", build.build_id) .eq("user_id", user.user_id); if (!error) { setLikedBuilds(prev => prev.filter(id => id !== build.build_id)); setBuilds(prev => prev.map(b => b.build_id === build.build_id ? { ...b, likes: (b.likes || 1) - 1 } : b) ); } } else { const { error } = await supabase .from("build_likes") .insert({ build_id: build.build_id, user_id: user.user_id }); if (!error) { setLikedBuilds(prev => [...prev, build.build_id]); setBuilds(prev => prev.map(b => b.build_id === build.build_id ? { ...b, likes: (b.likes || 0) + 1 } : b) ); // Send push notification to build owner (non-blocking) try { const payload = { userId: build.user_id, notificationType: 'buildLike', title: `${user.user_name} liked your build`, body: `${user.user_name} liked ${build.build_name}`, url: `/builds/${build.build_id}`, }; console.log('Sending push notification payload (mybuilds):', 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 (mybuilds):', r)).catch(() => {})) .catch(err => console.error('Error sending push (mybuilds):', err)); } catch (err) { console.error('Error preparing push send (mybuilds):', err); } } } }; const handleDeleteBuild = async () => { if (!buildToDelete) return; try { const { error } = await supabase .from("builds") .delete() .eq("build_id", buildToDelete.build_id); if (error) throw error; // Remove from local state setBuilds(prev => prev.filter(b => b.build_id !== buildToDelete.build_id)); setDeletePopupOpen(false); setBuildToDelete(null); } catch (err: any) { console.error("Failed to delete build:", err); swal.error("Failed to delete build", err.message); } }; const getFilteredBuilds = (): BuildWithDetails[] => { let filtered = builds.filter( b => b.build_name.toLowerCase().includes(searchTerm.toLowerCase()) || b.build_types?.type_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 } }) } 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() // Full-page loading only while we don't know auth yet if (authLoading) { return (

Loading...

) } if (!user) { return (

Sign in to see your builds

Log in or create an account to view and manage your PC builds.

) } // From here we have user: show page shell; loading only in list area const listLoading = loading && builds.length === 0 && !loadingTimeout const listError = loadingTimeout && builds.length === 0 && !error return (
{error && (

{error}

)} {/* Stats */}
{formatNumber(statistics.totalBuilds)} builds
{formatNumber(statistics.totalLikes)} likes
setSearchTerm(e.target.value)} />
{listError ? (

Taking longer than usual

Check your connection or try again.

) : listLoading ? (

Loading builds...

) : (
{filteredBuilds.map((build) => { const creator = build.users const isLiked = likedBuilds.includes(build.build_id) const cpuComponent = build.components?.find((bc: any) => bc.components?.component_categories?.category_name === 'CPU' ) const gpuComponent = build.components?.find((bc: any) => bc.components?.component_categories?.category_name === 'GPU' ) return (
{build.build_name}
{creator?.user_name?.charAt(0).toUpperCase() || "U"} {creator?.user_name || "Unknown"} {creator?.user_name || "Unknown"}
{formatCurrency(build.totalPrice || 0)}
{build.date_created ? new Date(build.date_created).toLocaleDateString() : "-"}
{(build.views || 0).toLocaleString()} views

{build?.description || 'PC Build'}

{/* Build tags */}
{build.build_types?.type_name || 'build'}
{/* Key components */}
{cpuComponent && (
{cpuComponent.components?.component_name}
)} {gpuComponent && (
{gpuComponent.components?.component_name}
)}
{/* Actions */}
) })}
)} {filteredBuilds.length === 0 && !loading && (

No builds found

Try adjusting your search terms or filters

)}
{/* Popup for trying to like own build */} {likeOwnPopupOpen && (

Oops!

You cannot like your own build.

)} {/* Share Popup */} {sharePopupOpen && (

Share Build

Copy the link below to share this build with others.

)} {deletePopupOpen && buildToDelete && (

Confirm Delete

Are you sure you want to delete "{buildToDelete.build_name}"? This action cannot be undone.

)}
) }