"use client"; import React, { useState, useEffect } from "react"; import { useRouter, useSearchParams, usePathname } from "next/navigation"; import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { supabase } from "@/lib/supabase"; import { swal } from "@/lib/sweetalert"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Cpu, HardDrive, MemoryStick, Zap, Monitor, Fan, Clapperboard as Motherboard, Save, Share, AlertTriangle, CheckCircle, Search, ArrowLeft, AlertCircle, Info, Lightbulb, MessageSquare, TrendingUp, Loader2, CheckCircle2, ShoppingCart, MapPin, Download, Edit, HelpCircle, BookOpen, Plus, } from "lucide-react"; import { type Component, type ComponentCategory, type PerformanceCategory, performanceCategories, } from "@/lib/mock-data"; import { CompatibilityChecker, type CompatibilityResult, } from "@/lib/compatibility-checker"; import { filterComponentsByPerformance } from "@/lib/performance-filter"; import { DuplicateDetector, type BuildComparison, } from "@/lib/duplicate-detector"; import { DuplicateCheckDialog } from "@/components/duplicate-warning"; import { formatCurrency } from "@/lib/currency"; import { getCSPRecommendations, getUpgradeRecommendations, type CSPSolution, } from "@/lib/algorithm-service"; import { getSupabaseComponents, getSupabaseComponentsByCategory, } from "@/lib/supabase-components"; import { useAuth } from "@/contexts/supabase-auth-context"; import { componentService } from "@/lib/database"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { RetailerLocation } from "@/components/retailer-location"; import { BuildWizard } from "@/components/build-wizard"; const categoryIcons = { cpu: Cpu, motherboard: Motherboard, memory: MemoryStick, storage: HardDrive, gpu: Monitor, psu: Zap, case: HardDrive, cooling: Fan, }; const categoryNames = { cpu: "Processors", motherboard: "Motherboard", memory: "Memory (RAM)", storage: "Storage", gpu: "Graphics Card", psu: "Power Supply", case: "Case", cooling: "Cooling", }; export default function BuilderPage() { const { user } = useAuth(); const router = useRouter(); const searchParams = useSearchParams(); const cloneBuildId = searchParams.get("clone"); const [selectedComponents, setSelectedComponents] = useState< Record >({ cpu: null, motherboard: null, memory: null, storage: null, gpu: null, psu: null, case: null, cooling: null, }); const [components, setComponents] = useState([]); const [isLoadingComponents, setIsLoadingComponents] = useState(false); const [fetchError, setFetchError] = useState(null); const [activeCategory, setActiveCategory] = useState("cpu"); const [componentPage, setComponentPage] = useState(0); const COMPONENTS_PER_PAGE = 10; const [searchTerm, setSearchTerm] = useState(""); const [locationFilter, setLocationFilter] = useState(""); const [showOnlyCompatible, setShowOnlyCompatible] = useState(true); // Default to true for automatic filtering const [sortBy, setSortBy] = useState<"default" | "price-low" | "price-high">( "default", ); const [showBuildWizard, setShowBuildWizard] = useState(false); const [userExperienceLevel, setUserExperienceLevel] = useState< "beginner" | "intermediate" | "advanced" >("beginner"); const [buildName, setBuildName] = useState("My Custom Build"); const [buildType, setBuildType] = useState("4"); const [isSaveDialogOpen, setIsSaveDialogOpen] = useState(false); const [savedBuildId, setSavedBuildId] = useState(null); const [showSuccessDialog, setShowSuccessDialog] = useState(false); const [showPurchaseConfirm, setShowPurchaseConfirm] = useState(false); const [performanceCategory, setPerformanceCategory] = useState("all"); const [budget, setBudget] = useState(0); const [budgetEnabled, setBudgetEnabled] = useState(false); const [duplicateComparisons, setDuplicateComparisons] = useState< BuildComparison[] >([]); const [showDuplicateDialog, setShowDuplicateDialog] = useState(false); const [isCheckingDuplicates, setIsCheckingDuplicates] = useState(false); const [cspSolutions, setCspSolutions] = useState([]); const [isCSPDialogOpen, setIsCSPDialogOpen] = useState(false); const [isLoadingCSP, setIsLoadingCSP] = useState(false); const [upgradeRecommendations, setUpgradeRecommendations] = useState( [], ); const [upgradeCategoryMap, setUpgradeCategoryMap] = useState< Map >(new Map()); const [isLoadingUpgrades, setIsLoadingUpgrades] = useState(false); const [showUpgradeDialog, setShowUpgradeDialog] = useState(false); const [algorithmError, setAlgorithmError] = useState(null); const [cspLoadingStartTime, setCspLoadingStartTime] = useState( null, ); // Modal state for component selection const [isComponentModalOpen, setIsComponentModalOpen] = useState(false); const [modalCategory, setModalCategory] = useState(null); // Debug: Log when dialog state changes useEffect(() => { console.log("🔍 CSP Dialog state:", isCSPDialogOpen); }, [isCSPDialogOpen]); const [buildDescription, setBuildDescription] = useState(""); const [showPurchasePreview, setShowPurchasePreview] = useState(false); const [showImportDialog, setShowImportDialog] = useState(false); const [importSearchTerm, setImportSearchTerm] = useState(""); const [availableBuilds, setAvailableBuilds] = useState([]); const [isLoadingBuilds, setIsLoadingBuilds] = useState(false); const [selectedComponentDetails, setSelectedComponentDetails] = useState(null); const [showComponentDetailsDialog, setShowComponentDetailsDialog] = useState(false); const [editingImage, setEditingImage] = useState(false); const [componentImageUrl, setComponentImageUrl] = useState(""); const [isUpdatingComponentImage, setIsUpdatingComponentImage] = useState(false); const [cspPage, setCspPage] = useState(0); const SOLUTIONS_PER_PAGE = 10; const [cspHasMore, setCspHasMore] = useState(false); const [isLoadingCSPPage, setIsLoadingCSPPage] = useState(false); // Category mapping from database category_id to app category const categoryIdToAppCategory: Record = { 1: "cpu", 2: "motherboard", 3: "memory", 4: "storage", 5: "gpu", 6: "psu", 7: "case", 8: "cooling", }; //Clone Build useEffect(() => { const cloneBuildId = searchParams.get("clone"); if (!cloneBuildId) return; const importCloneBuild = async () => { try { const buildId = Number(cloneBuildId); if (isNaN(buildId)) { console.error("Invalid build ID in clone parameter"); router.replace("/builder"); return; } // Fetch the build from Supabase const { data: buildData, error } = await supabase .from("builds") .select( ` *, users(user_id, user_name, email), build_types(build_type_id, type_name), build_components( component_id, components(*) ) `, ) .eq("build_id", buildId) .single(); if (error || !buildData) { console.error("Error fetching clone build:", error); swal.error("Build not found", "The cloned build may have been deleted."); router.replace("/builder"); return; } // Map fetched components into the correct category const mappedComponents: Record = { cpu: null, motherboard: null, memory: null, storage: null, gpu: null, psu: null, case: null, cooling: null, }; (buildData.build_components || []).forEach((bc: any) => { if (!bc?.components) return; const category = categoryIdToAppCategory[bc.components.category_id]; if (category) mappedComponents[category] = bc.components; }); // Reuse the shared build import function const buildForImport = { ...buildData, components: (buildData.build_components || []) .map((bc: any) => bc.components) .filter(Boolean), totalPrice: (buildData.build_components || []).reduce( (sum: number, bc: any) => sum + (Number(bc.components?.component_price) || 0), 0, ), }; await handleImportBuild(buildForImport); // Remove the clone parameter from the URL router.replace("/builder"); } catch (err) { console.error("Failed to import clone build:", err); swal.error("Failed to clone build", "Please try again."); router.replace("/builder"); } }; importCloneBuild(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams]); // Fetch available builds for import useEffect(() => { if (!showImportDialog) return; const fetchBuilds = async () => { setIsLoadingBuilds(true); try { const { data: buildsData, error } = await supabase .from("builds") .select( ` *, users(user_id, user_name, email), build_types(build_type_id, type_name), build_components( component_id, components(*) ) `, ) .order("date_created", { ascending: false }) .limit(50); if (error) throw error; // Filter builds by search term and calculate total price const filteredBuilds = (buildsData || []) .filter((build) => { if (!importSearchTerm.trim()) return true; const searchLower = importSearchTerm.toLowerCase(); return ( build.build_name?.toLowerCase().includes(searchLower) || build.users?.user_name?.toLowerCase().includes(searchLower) ); }) .map((build) => { const components = build.build_components || []; const totalPrice = components.reduce((sum: number, bc: any) => { return sum + (Number(bc.components?.component_price) || 0); }, 0); return { ...build, components, totalPrice, }; }); setAvailableBuilds(filteredBuilds); } catch (error) { console.error("Error fetching builds:", error); setAvailableBuilds([]); } finally { setIsLoadingBuilds(false); } }; fetchBuilds(); }, [showImportDialog, importSearchTerm]); // Auto-import shared build from URL parameter useEffect(() => { const shareBuildId = searchParams.get("share"); if (!shareBuildId) return; const importSharedBuild = async () => { try { const buildId = Number(shareBuildId); if (isNaN(buildId)) { console.error("Invalid build ID in share parameter"); router.replace("/builder"); return; } // Fetch the build from Supabase const { data: buildData, error } = await supabase .from("builds") .select( ` *, users(user_id, user_name, email), build_types(build_type_id, type_name), build_components( component_id, components(*) ) `, ) .eq("build_id", buildId) .single(); if (error || !buildData) { console.error("Error fetching shared build:", error); swal.error("Build not found", "The shared build may have been deleted."); router.replace("/builder"); return; } // Calculate total price const components = buildData.build_components || []; const totalPrice = components.reduce((sum: number, bc: any) => { return sum + (Number(bc.components?.component_price) || 0); }, 0); const build = { ...buildData, components, totalPrice, }; // Import the build using handleImportBuild logic await handleImportBuild(build); // Remove the share parameter from URL after importing router.replace("/builder"); } catch (error) { console.error("Error importing shared build:", error); swal.error("Failed to import shared build", "Please try again."); router.replace("/builder"); } }; importSharedBuild(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams]); // Load build for editing when ?edit=build_id is present (e.g. from My Builds) useEffect(() => { const editBuildId = searchParams.get("edit"); if (!editBuildId || !user) return; const loadBuildForEdit = async () => { try { const buildId = Number(editBuildId); if (isNaN(buildId)) { router.replace("/builder"); return; } const { data: buildData, error } = await supabase .from("builds") .select( ` *, users(user_id, user_name, email), build_types(build_type_id, type_name), build_components( component_id, components(*) ) `, ) .eq("build_id", buildId) .single(); if (error || !buildData) { console.error("Error fetching build for edit:", error); swal.error("Build not found", "It may have been deleted."); router.replace("/mybuilds"); return; } // Only the owner can edit if (buildData.user_id !== user.user_id) { swal.warning("Cannot edit", "You can only edit your own builds."); router.replace("/mybuilds"); return; } const buildForImport = { ...buildData, components: (buildData.build_components || []) .map((bc: any) => bc.components) .filter(Boolean), totalPrice: (buildData.build_components || []).reduce( (sum: number, bc: any) => sum + (Number(bc.components?.component_price) || 0), 0, ), }; await handleImportBuild(buildForImport, { silent: true }); setSavedBuildId(buildId); } catch (err) { console.error("Failed to load build for edit:", err); swal.error("Failed to load build", "Please try again."); router.replace("/mybuilds"); } }; loadBuildForEdit(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams, user]); // Check if a component is compatible with currently selected components const isComponentCompatible = ( component: Component, category: ComponentCategory, ): boolean => { // If no components are selected, show all components const hasSelectedComponents = Object.values(selectedComponents).some( (comp) => comp !== null, ); if (!hasSelectedComponents) { return true; // Show all when nothing is selected } // Note: We removed the "show all if same category" logic to properly check compatibility // even when replacing components in the same category // CPU-Motherboard compatibility if (category === "motherboard" && selectedComponents.cpu) { // Skip checking against itself if we're replacing the motherboard const cpuSocket = selectedComponents.cpu.compatibility.socket; const mbSocket = component.compatibility.socket; if (cpuSocket && mbSocket && cpuSocket !== mbSocket) { return false; } } if (category === "cpu" && selectedComponents.motherboard) { // When viewing CPUs, check against the selected motherboard const cpuSocket = component.compatibility.socket; const mbSocket = selectedComponents.motherboard.compatibility.socket; if (cpuSocket && mbSocket && cpuSocket !== mbSocket) { return false; } } // Memory-Motherboard compatibility if (category === "memory" && selectedComponents.motherboard) { const memoryType = component.compatibility.memoryType || (component.specifications.type as string); const mbMemoryType = selectedComponents.motherboard.compatibility.memoryType || (selectedComponents.motherboard.specifications.memoryType as string); if (memoryType && mbMemoryType) { const mbSupports = Array.isArray(mbMemoryType) ? mbMemoryType : [mbMemoryType]; if (!mbSupports.includes(memoryType)) { return false; } } } if (category === "motherboard" && selectedComponents.memory) { const memoryType = selectedComponents.memory.compatibility.memoryType || (selectedComponents.memory.specifications.type as string); const mbMemoryType = component.compatibility.memoryType || (component.specifications.memoryType as string); if (memoryType && mbMemoryType) { const mbSupports = Array.isArray(mbMemoryType) ? mbMemoryType : [mbMemoryType]; if (!mbSupports.includes(memoryType)) { return false; } } } // GPU-Case compatibility (dimensions) if (category === "gpu" && selectedComponents.case) { const gpuLength = component.compatibility.dimensions?.length || 0; const caseMaxGpuLength = Number.parseInt( selectedComponents.case.specifications.maxGpuLength as string, ) || 0; if ( gpuLength > 0 && caseMaxGpuLength > 0 && gpuLength > caseMaxGpuLength ) { return false; } const gpuHeight = component.compatibility.dimensions?.height || 0; const caseMaxGpuHeight = Number.parseInt( selectedComponents.case.specifications.maxCoolerHeight as string, ) || 0; if ( gpuHeight > 0 && caseMaxGpuHeight > 0 && gpuHeight > caseMaxGpuHeight ) { return false; } } if (category === "case" && selectedComponents.gpu) { const gpuLength = selectedComponents.gpu.compatibility.dimensions?.length || 0; const caseMaxGpuLength = Number.parseInt(component.specifications.maxGpuLength as string) || 0; if ( gpuLength > 0 && caseMaxGpuLength > 0 && gpuLength > caseMaxGpuLength ) { return false; } const gpuHeight = selectedComponents.gpu.compatibility.dimensions?.height || 0; const caseMaxGpuHeight = Number.parseInt(component.specifications.maxCoolerHeight as string) || 0; if ( gpuHeight > 0 && caseMaxGpuHeight > 0 && gpuHeight > caseMaxGpuHeight ) { return false; } } // Cooling-CPU compatibility (socket) if (category === "cooling" && selectedComponents.cpu) { const cpuSocket = selectedComponents.cpu.compatibility.socket; if (cpuSocket && cpuSocket !== "Standard") { // Try to get supported sockets from cooling component let supportedSockets: string[] = []; // Method 1: Check compatibility.socket (may be comma-separated string like "AM4,LGA1700") if (component.compatibility.socket) { const socketStr = component.compatibility.socket; if (socketStr.includes(",")) { supportedSockets = socketStr.split(",").map((s) => s.trim()); } else if (socketStr !== "Standard") { supportedSockets = [socketStr]; } } // Method 2: Try parsing from Compatibility JSON in specifications if (supportedSockets.length === 0) { try { const compatStr = component.specifications.Compatibility as string; if (typeof compatStr === "string") { const compat = JSON.parse(compatStr); if (compat.supportedSockets) { supportedSockets = Array.isArray(compat.supportedSockets) ? compat.supportedSockets : [compat.supportedSockets]; } else if (compat.supported_sockets) { supportedSockets = Array.isArray(compat.supported_sockets) ? compat.supported_sockets : [compat.supported_sockets]; } } } catch (e) { // Ignore parse errors } } // Method 3: Check specifications for supportedSockets field (JSON string) if ( supportedSockets.length === 0 && component.specifications.supportedSockets ) { try { const parsed = JSON.parse( component.specifications.supportedSockets as string, ); if (Array.isArray(parsed)) { supportedSockets = parsed; } else if (typeof parsed === "string") { // Handle comma-separated string supportedSockets = parsed.includes(",") ? parsed.split(",").map((s) => s.trim()) : [parsed]; } } catch (e) { // Not a JSON string, try as direct array or string if (Array.isArray(component.specifications.supportedSockets)) { supportedSockets = component.specifications .supportedSockets as string[]; } else if ( typeof component.specifications.supportedSockets === "string" ) { const socketStr = component.specifications.supportedSockets; supportedSockets = socketStr.includes(",") ? socketStr.split(",").map((s) => s.trim()) : [socketStr]; } } } // Check compatibility - if we have socket info and it doesn't match, filter out if ( supportedSockets.length > 0 && !supportedSockets.includes(cpuSocket) ) { return false; } } } // CPU-Cooling compatibility (when CPU is selected, filter coolers) if (category === "cpu" && selectedComponents.cooling) { const cpuSocket = component.compatibility.socket; if (cpuSocket && cpuSocket !== "Standard") { let supportedSockets: string[] = []; // Method 1: Check compatibility.socket (may be comma-separated string) if (selectedComponents.cooling.compatibility.socket) { const socketStr = selectedComponents.cooling.compatibility.socket; if (socketStr.includes(",")) { supportedSockets = socketStr.split(",").map((s) => s.trim()); } else if (socketStr !== "Standard") { supportedSockets = [socketStr]; } } // Method 2: Try parsing from Compatibility JSON if (supportedSockets.length === 0) { try { const compatStr = selectedComponents.cooling.specifications .Compatibility as string; if (typeof compatStr === "string") { const compat = JSON.parse(compatStr); if (compat.supportedSockets) { supportedSockets = Array.isArray(compat.supportedSockets) ? compat.supportedSockets : [compat.supportedSockets]; } else if (compat.supported_sockets) { supportedSockets = Array.isArray(compat.supported_sockets) ? compat.supported_sockets : [compat.supported_sockets]; } } } catch (e) { // Ignore parse errors } } // Method 3: Check specifications for supportedSockets if ( supportedSockets.length === 0 && selectedComponents.cooling.specifications.supportedSockets ) { try { const parsed = JSON.parse( selectedComponents.cooling.specifications .supportedSockets as string, ); if (Array.isArray(parsed)) { supportedSockets = parsed; } else if (typeof parsed === "string") { supportedSockets = parsed.includes(",") ? parsed.split(",").map((s) => s.trim()) : [parsed]; } } catch (e) { if ( Array.isArray( selectedComponents.cooling.specifications.supportedSockets, ) ) { supportedSockets = selectedComponents.cooling.specifications .supportedSockets as string[]; } else if ( typeof selectedComponents.cooling.specifications .supportedSockets === "string" ) { const socketStr = selectedComponents.cooling.specifications.supportedSockets; supportedSockets = socketStr.includes(",") ? socketStr.split(",").map((s) => s.trim()) : [socketStr]; } } } if ( supportedSockets.length > 0 && !supportedSockets.includes(cpuSocket) ) { return false; } } } // Cooling-Case compatibility (height) if (category === "cooling" && selectedComponents.case) { const coolerHeight = Number.parseInt(component.specifications.height as string) || 0; const caseMaxHeight = Number.parseInt( selectedComponents.case.specifications.maxCoolerHeight as string, ) || 0; if ( coolerHeight > 0 && caseMaxHeight > 0 && coolerHeight > caseMaxHeight ) { return false; } } if (category === "case" && selectedComponents.cooling) { const coolerHeight = Number.parseInt( selectedComponents.cooling.specifications.height as string, ) || 0; const caseMaxHeight = Number.parseInt(component.specifications.maxCoolerHeight as string) || 0; if ( coolerHeight > 0 && caseMaxHeight > 0 && coolerHeight > caseMaxHeight ) { return false; } } // Storage-Motherboard compatibility (M.2/SATA slots) if (category === "storage" && selectedComponents.motherboard) { const storageInterface = component.specifications.interface as string; if (storageInterface === "M.2") { const m2Slots = Number.parseInt( selectedComponents.motherboard.specifications.m2Slots as string, ) || 0; if (m2Slots === 0) { return false; } } if (storageInterface === "SATA") { const sataPorts = Number.parseInt( selectedComponents.motherboard.specifications.sataPorts as string, ) || 0; if (sataPorts === 0) { return false; } } } if (category === "motherboard" && selectedComponents.storage) { const storageInterface = selectedComponents.storage.specifications .interface as string; if (storageInterface === "M.2") { const m2Slots = Number.parseInt(component.specifications.m2Slots as string) || 0; if (m2Slots === 0) { return false; } } if (storageInterface === "SATA") { const sataPorts = Number.parseInt(component.specifications.sataPorts as string) || 0; if (sataPorts === 0) { return false; } } } // PSU-Power requirement compatibility if (category === "psu") { // Calculate total power requirement from selected components let totalPower = 0; if (selectedComponents.cpu) { const cpuTdp = Number.parseInt( selectedComponents.cpu.specifications.tdp as string, ) || 0; totalPower += cpuTdp; } if (selectedComponents.gpu) { const gpuTdp = Number.parseInt( selectedComponents.gpu.specifications.tdp as string, ) || 0; totalPower += gpuTdp; } // Add base power for other components (motherboard, RAM, storage, etc.) totalPower += 150; // Base power for other components const psuWattage = Number.parseInt(component.specifications.wattage as string) || 0; // PSU should have at least 20% headroom if (psuWattage > 0 && totalPower > 0 && psuWattage < totalPower * 1.2) { return false; // PSU wattage is insufficient } } // Case-Motherboard compatibility (form factor) if (category === "case" && selectedComponents.motherboard) { const mbFormFactor = selectedComponents.motherboard.compatibility.formFactor || (selectedComponents.motherboard.specifications.formFactor as string) || (selectedComponents.motherboard.specifications[ "Form Factor" ] as string); const caseFormFactor = component.compatibility.formFactor || (component.specifications.formFactor as string) || (component.specifications["Form Factor"] as string) || (component.specifications["Supported Form Factors"] as string); if (mbFormFactor && caseFormFactor) { // Normalize form factors (ATX, Micro ATX, Mini ITX, etc.) const mbFormFactorLower = mbFormFactor .toLowerCase() .replace(/\s+/g, ""); const caseFormFactorLower = caseFormFactor .toLowerCase() .replace(/\s+/g, ""); // Check if case supports the motherboard form factor // Cases typically support multiple form factors (e.g., "ATX, Micro ATX, Mini ITX") if (caseFormFactorLower.includes(",")) { // Multiple form factors supported const supportedFormFactors = caseFormFactorLower .split(",") .map((f) => f.trim()); if ( !supportedFormFactors.some( (f) => f.includes(mbFormFactorLower) || mbFormFactorLower.includes(f), ) ) { return false; } } else { // Single form factor - must match if ( !caseFormFactorLower.includes(mbFormFactorLower) && !mbFormFactorLower.includes(caseFormFactorLower) ) { return false; } } } } if (category === "motherboard" && selectedComponents.case) { const mbFormFactor = component.compatibility.formFactor || (component.specifications.formFactor as string) || (component.specifications["Form Factor"] as string); const caseFormFactor = selectedComponents.case.compatibility.formFactor || (selectedComponents.case.specifications.formFactor as string) || (selectedComponents.case.specifications["Form Factor"] as string) || (selectedComponents.case.specifications[ "Supported Form Factors" ] as string); if (mbFormFactor && caseFormFactor) { const mbFormFactorLower = mbFormFactor .toLowerCase() .replace(/\s+/g, ""); const caseFormFactorLower = caseFormFactor .toLowerCase() .replace(/\s+/g, ""); if (caseFormFactorLower.includes(",")) { const supportedFormFactors = caseFormFactorLower .split(",") .map((f) => f.trim()); if ( !supportedFormFactors.some( (f) => f.includes(mbFormFactorLower) || mbFormFactorLower.includes(f), ) ) { return false; } } else { if ( !caseFormFactorLower.includes(mbFormFactorLower) && !mbFormFactorLower.includes(caseFormFactorLower) ) { return false; } } } } // Cooling-Motherboard compatibility (when motherboard is selected, filter coolers by socket) if (category === "cooling" && selectedComponents.motherboard) { const mbSocket = selectedComponents.motherboard.compatibility.socket; if (mbSocket && mbSocket !== "Standard") { let supportedSockets: string[] = []; // Method 1: Check compatibility.socket if (component.compatibility.socket) { const socketStr = component.compatibility.socket; if (socketStr.includes(",")) { supportedSockets = socketStr.split(",").map((s) => s.trim()); } else if (socketStr !== "Standard") { supportedSockets = [socketStr]; } } // Method 2: Try parsing from Compatibility JSON if (supportedSockets.length === 0) { try { const compatStr = component.specifications.Compatibility as string; if (typeof compatStr === "string") { const compat = JSON.parse(compatStr); if (compat.supportedSockets) { supportedSockets = Array.isArray(compat.supportedSockets) ? compat.supportedSockets : [compat.supportedSockets]; } else if (compat.supported_sockets) { supportedSockets = Array.isArray(compat.supported_sockets) ? compat.supported_sockets : [compat.supported_sockets]; } } } catch (e) { // Ignore parse errors } } // Method 3: Check specifications for supportedSockets if ( supportedSockets.length === 0 && component.specifications.supportedSockets ) { try { const parsed = JSON.parse( component.specifications.supportedSockets as string, ); if (Array.isArray(parsed)) { supportedSockets = parsed; } else if (typeof parsed === "string") { supportedSockets = parsed.includes(",") ? parsed.split(",").map((s) => s.trim()) : [parsed]; } } catch (e) { if (Array.isArray(component.specifications.supportedSockets)) { supportedSockets = component.specifications .supportedSockets as string[]; } else if ( typeof component.specifications.supportedSockets === "string" ) { const socketStr = component.specifications.supportedSockets; supportedSockets = socketStr.includes(",") ? socketStr.split(",").map((s) => s.trim()) : [socketStr]; } } } // If we have socket info and it doesn't match, filter out if ( supportedSockets.length > 0 && !supportedSockets.includes(mbSocket) ) { return false; } } } // If we reach here, component is compatible (or no compatibility check applies) return true; }; const getFilteredComponents = (category: ComponentCategory) => { const categoryFiltered = components.filter( (component) => component.category === category, ); // Debug: Log GPU components if (category === "gpu") { console.log( "GPU Components found:", categoryFiltered.length, categoryFiltered.map((c) => ({ name: c.name, price: c.price, category: c.category, })), ); } // Filter out components with no price (price is 0, null, or undefined) const priceFiltered = categoryFiltered.filter( (component) => component.price && component.price > 0, ); // Debug: Log filtered GPU components if (category === "gpu") { console.log("GPU Components after price filter:", priceFiltered.length); } const performanceFiltered = filterComponentsByPerformance( priceFiltered, performanceCategory, performanceCategories[performanceCategory].requirements, ); const searchFiltered = performanceFiltered.filter( (component) => component.name.toLowerCase().includes(searchTerm.toLowerCase()) || component.brand.toLowerCase().includes(searchTerm.toLowerCase()), ); // Location-based filtering const locationFiltered = locationFilter ? searchFiltered.filter( (component) => component.retailer?.address ?.toLowerCase() .includes(locationFilter.toLowerCase()) || component.retailer?.name ?.toLowerCase() .includes(locationFilter.toLowerCase()), ) : searchFiltered; // Compatibility filtering - if enabled, only show compatible components const compatibilityFiltered = showOnlyCompatible ? locationFiltered.filter((component) => isComponentCompatible(component, category), ) : locationFiltered; // Sort components by price let sortedComponents = [...compatibilityFiltered]; if (sortBy === "price-low") { sortedComponents.sort((a, b) => a.price - b.price); } else if (sortBy === "price-high") { sortedComponents.sort((a, b) => b.price - a.price); } // "default" keeps original order return sortedComponents; }; const totalPrice = Object.values(selectedComponents) .filter(Boolean) .reduce((sum, component) => sum + component!.price, 0); const remainingBudget = budgetEnabled ? budget - totalPrice : 0; const isOverBudget = budgetEnabled && totalPrice > budget; const budgetPercentage = budgetEnabled && budget > 0 ? (totalPrice / budget) * 100 : 0; const getCompatibilityResult = (): CompatibilityResult => { const checker = new CompatibilityChecker(selectedComponents); return checker.checkCompatibility(); }; const handleComponentSelect = (component: Component) => { setSelectedComponents((prev) => ({ ...prev, [component.category]: component, })); }; const handleComponentClick = ( component: Component, e: React.MouseEvent, ) => { // If Ctrl/Cmd is pressed, open details dialog if (e.ctrlKey || e.metaKey) { e.preventDefault(); setSelectedComponentDetails(component); setComponentImageUrl(component.image || ""); setShowComponentDetailsDialog(true); } else { // Normal click - select component handleComponentSelect(component); } }; const handleUpdateComponentImage = async () => { if (!selectedComponentDetails || !user || user.user_type !== "admin") return; try { setIsUpdatingComponentImage(true); // Extract component ID from the component.id (format: "component-{id}") const componentId = parseInt( selectedComponentDetails.id.replace("component-", ""), ); await componentService.update(componentId, { component_image: componentImageUrl || null, }); // Update the component in the local state setComponents((prev) => prev.map((comp) => comp.id === selectedComponentDetails.id ? { ...comp, image: componentImageUrl || "/placeholder.svg" } : comp, ), ); // Update selected component details setSelectedComponentDetails({ ...selectedComponentDetails, image: componentImageUrl || "/placeholder.svg", }); setEditingImage(false); swal.success("Image updated", "Component image updated successfully!"); } catch (error) { console.error("Error updating component image:", error); swal.error("Update failed", "Failed to update component image. Please try again."); } finally { setIsUpdatingComponentImage(false); } }; const handleComponentRemove = (category: ComponentCategory) => { setSelectedComponents((prev) => ({ ...prev, [category]: null, })); }; const handleGetCSPRecommendations = async (page: number = 0) => { if (!budgetEnabled || budget <= 0) { setAlgorithmError("Please set a budget first"); return; } if (budget < 10000) { setAlgorithmError( "CSP recommendations require a minimum budget of ₱10,000", ); return; } setIsLoadingCSP(true); setIsLoadingCSPPage(true); setAlgorithmError(null); // Track loading start time for timeout let timeInterval: NodeJS.Timeout | null = null; const startTime = Date.now(); const MIN_LOADING_TIME = page === 0 ? 120000 : 60000; // 2 minutes for first page, 1 minute for subsequent pages if (page === 0) { setCspLoadingStartTime(startTime); } // Open dialog immediately when starting (for first page only) if (page === 0) { console.log("Opening CSP dialog...", { isCSPDialogOpen }); setIsCSPDialogOpen(true); console.log("CSP dialog state set to true"); } try { // Build user_inputs map - extract numeric ID from component-{id} format // This includes both manually selected components and imported build components const userInputs: Record = {}; if (selectedComponents.cpu) { userInputs["CPU"] = typeof selectedComponents.cpu.id === "number" ? selectedComponents.cpu.id : parseInt(String(selectedComponents.cpu.id).replace(/\D/g, "")) || 0; } if (selectedComponents.gpu) { userInputs["Video Card"] = typeof selectedComponents.gpu.id === "number" ? selectedComponents.gpu.id : parseInt(String(selectedComponents.gpu.id).replace(/\D/g, "")) || 0; } if (selectedComponents.motherboard) { userInputs["Motherboard"] = typeof selectedComponents.motherboard.id === "number" ? selectedComponents.motherboard.id : parseInt( String(selectedComponents.motherboard.id).replace(/\D/g, ""), ) || 0; } if (selectedComponents.memory) { userInputs["Memory"] = typeof selectedComponents.memory.id === "number" ? selectedComponents.memory.id : parseInt( String(selectedComponents.memory.id).replace(/\D/g, ""), ) || 0; } if (selectedComponents.storage) { userInputs["Storage"] = typeof selectedComponents.storage.id === "number" ? selectedComponents.storage.id : parseInt( String(selectedComponents.storage.id).replace(/\D/g, ""), ) || 0; } if (selectedComponents.psu) { userInputs["Power Supply"] = typeof selectedComponents.psu.id === "number" ? selectedComponents.psu.id : parseInt(String(selectedComponents.psu.id).replace(/\D/g, "")) || 0; } if (selectedComponents.case) { userInputs["Case"] = typeof selectedComponents.case.id === "number" ? selectedComponents.case.id : parseInt(String(selectedComponents.case.id).replace(/\D/g, "")) || 0; } if (selectedComponents.cooling) { userInputs["CPU Cooler"] = typeof selectedComponents.cooling.id === "number" ? selectedComponents.cooling.id : parseInt( String(selectedComponents.cooling.id).replace(/\D/g, ""), ) || 0; } // Use algorithm service with pagination, including performance category const algorithmStartTime = Date.now(); const result = await getCSPRecommendations( budget, userInputs, page, SOLUTIONS_PER_PAGE, performanceCategory !== "all" ? performanceCategory : undefined, ); // Ensure minimum loading time (1-2 minutes depending on page) const algorithmTime = Date.now() - algorithmStartTime; const remainingTime = MIN_LOADING_TIME - algorithmTime; if (remainingTime > 0) { // Wait for remaining time to meet minimum loading duration await new Promise((resolve) => setTimeout(resolve, remainingTime)); } if (page === 0) { // First page - replace all solutions setCspSolutions(result.solutions); } else { // Subsequent pages - append to existing solutions setCspSolutions((prev) => [...prev, ...result.solutions]); } setCspHasMore(result.hasMore); setCspPage(page); } catch (error: any) { console.error("Error getting CSP recommendations:", error); // Check if it's a timeout error if ( error.message?.includes("timeout") || error.message?.includes("aborted") || error.message?.includes("taking too long") ) { setAlgorithmError( "Request timed out after 3 minutes. The algorithm is taking too long. Try:\n- Reducing your budget\n- Removing some pre-selected components\n- Using a simpler build configuration", ); } else { setAlgorithmError( error.message || "Failed to get recommendations. Make sure Python backend is running.", ); } } finally { setIsLoadingCSP(false); setIsLoadingCSPPage(false); setCspLoadingStartTime(null); if (timeInterval) { clearInterval(timeInterval); } } }; const handleApplyCSPSolution = (solution: CSPSolution) => { // Map solution categories to ComponentCategory keys const newSelected: Record = { ...selectedComponents, }; // Category mapping from CSP solution to ComponentCategory const categoryMapping: Record = { cpu: "cpu", motherboard: "motherboard", "cpu cooler": "cooling", memory: "memory", storage: "storage", "video card": "gpu", case: "case", "power supply": "psu", }; Object.entries(solution).forEach(([category, comp]: [string, any]) => { // Convert the solution category string to ComponentCategory keys const normalizedCategory = category.toLowerCase(); const key = categoryMapping[normalizedCategory] || (normalizedCategory as ComponentCategory); if (key) { newSelected[key] = { id: comp.id, name: comp.name, brand: comp.brand || "", price: comp.price, category: key, image: comp.image || "", rating: comp.rating || 0, reviews: comp.reviews || 0, specifications: comp.specifications || {}, compatibility: comp.compatibility || {}, performanceTags: comp.performanceTags || (["all"] as PerformanceCategory[]), }; } }); setSelectedComponents(newSelected); setIsCSPDialogOpen(false); }; const handleGetUpgradeRecommendations = async () => { const selectedComponentsList = Object.values(selectedComponents).filter(Boolean); if (selectedComponentsList.length === 0) { setAlgorithmError( "Please select at least one component first to get upgrade suggestions.", ); setTimeout(() => setAlgorithmError(null), 5000); return; } setIsLoadingUpgrades(true); setAlgorithmError(null); setUpgradeRecommendations([]); try { // Build the current build array with proper component data const currentBuild = selectedComponentsList.map((comp) => { // Extract numeric ID from component let componentId = 0; if (typeof comp!.id === "number") { componentId = comp!.id; } else if (typeof comp!.id === "string") { const numericId = parseInt(String(comp!.id).replace(/\D/g, "")); componentId = isNaN(numericId) ? 0 : numericId; } return { component_id: componentId, component_name: comp!.name || comp!.brand + " " + comp!.name || "Unknown", component_price: comp!.price || 0, category_name: comp!.category.charAt(0).toUpperCase() + comp!.category.slice(1), }; }); console.log( "Requesting upgrade recommendations for build:", currentBuild, ); // Get upgrade recommendations from the algorithm service const recommendations = await getUpgradeRecommendations(currentBuild); console.log("Received upgrade recommendations:", recommendations); if (!recommendations || recommendations.length === 0) { setAlgorithmError( "No upgrade recommendations available for your current build.", ); setTimeout(() => setAlgorithmError(null), 5000); return; } setUpgradeRecommendations(recommendations); // Create a map of recommendation index to category by matching component names const categoryMap = new Map(); recommendations.forEach((rec, recIndex) => { // Find the component in selectedComponentsList that matches this recommendation const matchingComponent = selectedComponentsList.find((comp) => { const compName = comp!.name || comp!.brand + " " + comp!.name || ""; return ( compName .toLowerCase() .includes(rec.current_component.toLowerCase()) || rec.current_component.toLowerCase().includes(compName.toLowerCase()) ); }); if (matchingComponent) { categoryMap.set(recIndex, matchingComponent.category); } }); setUpgradeCategoryMap(categoryMap); // Show the dialog with recommendations setShowUpgradeDialog(true); console.log("✅ Upgrade recommendations dialog opened"); } catch (error: any) { console.error("Error getting upgrade recommendations:", error); const errorMessage = error.message || "Failed to get upgrade recommendations. Please make sure the Python backend is running and try again."; setAlgorithmError(errorMessage); setTimeout(() => setAlgorithmError(null), 8000); } finally { setIsLoadingUpgrades(false); } }; const handleApplyUpgrade = async ( recIndex: number, recommendedName: string, ) => { const category = upgradeCategoryMap.get(recIndex); if (!category) { setAlgorithmError( "Could not determine component category for upgrade. Please try again.", ); setTimeout(() => setAlgorithmError(null), 5000); return; } try { console.log( `Applying upgrade: ${recommendedName} to ${category} category`, ); // Fetch all components in this category from Supabase const categoryComponents = await getSupabaseComponentsByCategory(category); if (!categoryComponents || categoryComponents.length === 0) { setAlgorithmError( `No components found in ${category} category. Please try again.`, ); setTimeout(() => setAlgorithmError(null), 5000); return; } // Find the component by name (case-insensitive partial match) const upgradedComponent = categoryComponents.find((comp) => { const compFullName = `${comp.brand || ""} ${comp.name || ""}` .trim() .toLowerCase(); const recommendedLower = recommendedName.toLowerCase(); return ( compFullName.includes(recommendedLower) || recommendedLower.includes(compFullName) || comp.name.toLowerCase().includes(recommendedLower) || recommendedLower.includes(comp.name.toLowerCase()) ); }); if (!upgradedComponent) { setAlgorithmError( `Could not find component "${recommendedName}" in ${category} category. The component may not be available in the database.`, ); setTimeout(() => setAlgorithmError(null), 5000); return; } console.log("✅ Found upgrade component:", upgradedComponent); // Apply the upgrade to the selected components setSelectedComponents((prev) => ({ ...prev, [category]: upgradedComponent, })); // Show success message and close dialog setShowUpgradeDialog(false); setAlgorithmError(null); // Show a brief success indicator console.log( `✅ Successfully upgraded ${category} to ${upgradedComponent.name}`, ); } catch (error: any) { console.error("Error applying upgrade:", error); setAlgorithmError( error.message || "Failed to apply upgrade. Please try again.", ); setTimeout(() => setAlgorithmError(null), 5000); } }; const checkForDuplicates = async () => { console.log("🔍 Starting duplicate check..."); if (!user) { console.log("⚠️ No user, skipping duplicate check"); return true; } setIsCheckingDuplicates(true); try { const currentBuildFingerprint = DuplicateDetector.generateFingerprint(selectedComponents); console.log("🔍 Current build fingerprint:", currentBuildFingerprint); const mockExistingBuilds = [ { components: { cpu: "cpu-1", motherboard: "mobo-1", memory: "ram-1", storage: "ssd-1", gpu: "gpu-1", psu: "psu-1", case: "case-1", cooling: "cooling-1", }, totalPrice: 1200, componentCount: 8, priceRange: "mid" as const, performanceCategory: "gaming", }, ]; const comparisons = DuplicateDetector.checkForDuplicates( currentBuildFingerprint, mockExistingBuilds, ); console.log("🔍 Duplicate comparisons:", comparisons); if (comparisons.length > 0) { console.log("⚠️ Duplicates found, showing warning dialog"); setDuplicateComparisons(comparisons); setShowDuplicateDialog(true); return false; } console.log("✅ No duplicates found, proceeding with save"); return true; } catch (error) { console.error("❌ Error checking for duplicates:", error); // Allow save even if duplicate check fails return true; } finally { setIsCheckingDuplicates(false); } }; const handleSaveBuild = async () => { console.log("🚀 SAVE BUILD CLICKED!"); console.log("🔍 User:", user ? user.user_name : "NOT LOGGED IN"); console.log("🔍 Selected components:", selectedComponents); if (!user) { console.error("❌ No user logged in, redirecting to login"); router.push("/login"); return; } console.log("🔍 Checking for duplicates..."); const canProceed = await checkForDuplicates(); console.log("🔍 Duplicate check result:", canProceed); if (!canProceed) { console.log("⚠️ Duplicate check blocked save - showing duplicate dialog"); return; } console.log("✅ Duplicate check passed, proceeding with save..."); setIsSaveDialogOpen(false); const rowsToInsert = Object.values(selectedComponents) .filter((comp): comp is Component => comp !== null) .map((comp) => { const numericId = Number(String(comp.id).replace("component-", "")); return { component_id: numericId, }; }); let buildId: number; if (savedBuildId != null) { // Update existing build (edit mode) const { error: updateError } = await supabase .from("builds") .update({ build_name: buildName, total_price: totalPrice, build_type_id: parseInt(buildType), description: buildDescription, }) .eq("build_id", savedBuildId) .eq("user_id", user.user_id); if (updateError) { console.error("Error updating build:", updateError); setAlgorithmError("Failed to update build"); return; } // Remove existing build_components and insert new ones const { error: deleteError } = await supabase .from("build_components") .delete() .eq("build_id", savedBuildId); if (deleteError) { console.error("Error clearing build components:", deleteError); setAlgorithmError("Failed to update build components"); return; } const rowsWithBuildId = rowsToInsert.map((row) => ({ build_id: savedBuildId, component_id: row.component_id, })); if (rowsWithBuildId.length > 0) { const { error: bcError } = await supabase .from("build_components") .insert(rowsWithBuildId); if (bcError) { console.error("Error inserting build_components:", bcError); setAlgorithmError("Failed to save build components"); swal.error("Save failed", "Failed to save build components. Check console for details."); return; } } buildId = savedBuildId; console.log("✅ Build updated successfully:", { buildId }); } else { // Create new build const { data: buildData, error: buildError } = await supabase .from("builds") .insert({ build_name: buildName, user_id: user.user_id, total_price: totalPrice, build_type_id: parseInt(buildType), description: buildDescription, }) .select("build_id") .single(); if (buildError || !buildData) { console.error("Error creating build:", buildError); setAlgorithmError("Failed to save build"); return; } buildId = buildData.build_id; const rowsWithBuildId = rowsToInsert.map((row) => ({ build_id: buildId, component_id: row.component_id, })); console.log("Rows to insert into build_components:", rowsWithBuildId); const { data: bcData, error: bcError } = await supabase .from("build_components") .insert(rowsWithBuildId); if (bcError) { console.error("Error inserting build_components:", bcError); setAlgorithmError("Failed to save build components"); swal.error("Save failed", "Failed to save build components. Check console for details."); return; } console.log("✅ Build saved successfully:", { buildId, bcData }); setSavedBuildId(buildId); } console.log("🔍 About to show purchase confirmation dialog..."); setIsSaveDialogOpen(false); // Show purchase confirmation dialog setTimeout(() => { console.log("🎉 Triggering purchase confirmation dialog now!"); setShowPurchaseConfirm(true); console.log("🎉 showPurchaseConfirm state set to:", true); // Fallback alert if dialog doesn't render setTimeout(() => { const dialogExists = document.querySelector('[role="alertdialog"]'); if (!dialogExists) { console.error("❌ AlertDialog NOT rendered! Showing fallback..."); const wantsPurchase = confirm( `✅ Build Saved Successfully!\n\n` + `Build: ${buildName}\n` + `Price: ${formatCurrency(totalPrice)}\n` + `Build ID: #${buildId}\n\n` + `Do you want to purchase this build now?`, ); if (wantsPurchase) { router.push(`/purchase/${buildId}`); router.refresh(); } } else { console.log("✅ AlertDialog rendered successfully!"); } }, 1000); }, 300); }; const handleProceedAnyway = () => { console.log("Saving build despite duplicates:", { name: buildName, components: selectedComponents, totalPrice, }); setIsSaveDialogOpen(false); setShowDuplicateDialog(false); }; const handleModifyBuild = () => { setSelectedComponents((prev) => ({ ...prev, cooling: null })); setShowDuplicateDialog(false); }; const handleImportBuild = async ( build: any, options?: { silent?: boolean }, ) => { try { // Convert build components to app Component format const importedComponents: Record = { cpu: null, motherboard: null, memory: null, storage: null, gpu: null, psu: null, case: null, cooling: null, }; // Process each build component for (const bc of build.build_components || []) { if (!bc.components) continue; const dbComponent = bc.components; const categoryId = dbComponent.category_id; const appCategory = categoryIdToAppCategory[categoryId]; if (!appCategory) continue; // Convert database component to app component format let compatInfo: any = {}; try { const compatStr = dbComponent.compatibility_information; if (typeof compatStr === "string") { compatInfo = JSON.parse(compatStr); } else if (compatStr && typeof compatStr === "object") { compatInfo = compatStr; } } catch (e) { console.warn("Could not parse compatibility information:", e); } const dbCategoryName = dbComponent.component_categories?.category_name?.toLowerCase() || ""; const brand = dbComponent.component_name?.split(" - ")[0]?.trim() || "Unknown"; let memoryTypeValue = compatInfo.memoryType || compatInfo.ram_type; if (appCategory === "memory" && !memoryTypeValue) { memoryTypeValue = compatInfo.type; } // Extract wattage from component name if not in compatibility info (for PSUs) let extractedWattage = 0; if (appCategory === "psu") { const nameMatch = dbComponent.component_name?.match(/(\d+)\s*W/i); if (nameMatch) { extractedWattage = Number.parseInt(nameMatch[1]) || 0; } } const compatibility: Component["compatibility"] = { socket: compatInfo.socket || "Standard", formFactor: compatInfo.formFactor || (appCategory === "case" ? compatInfo.type : "Standard"), memoryType: Array.isArray(memoryTypeValue) ? memoryTypeValue[0] : memoryTypeValue || (appCategory === "memory" ? "DDR4" : undefined), powerRequirement: compatInfo.powerRequirement || compatInfo.wattage || compatInfo.tdp || extractedWattage || (appCategory === "psu" ? 500 : 100), dimensions: compatInfo.dimensions || { length: compatInfo.length || (appCategory === "gpu" ? 220 : 100), width: compatInfo.width || 100, height: compatInfo.height || (appCategory === "gpu" ? 120 : 50), }, memorySupport: compatInfo.memorySupport || compatInfo.ram_type || memoryTypeValue, m2Slots: compatInfo.m2Slots?.toString(), sataPorts: compatInfo.sataPorts?.toString(), }; // Map performance tags let performanceTags: PerformanceCategory[] = ["all"]; if (dbComponent.component_purpose) { const mapPerf = { academic: "academic", gaming: "gaming", office: "office", } as Record; const raw = dbComponent.component_purpose.toLowerCase().trim(); if (mapPerf[raw]) { performanceTags = ["all", mapPerf[raw]]; } } // Extract retailer information const retailer = dbComponent.retailers ? { id: dbComponent.retailers.retailer_id, name: dbComponent.retailers.retailer_name || "Central Juan Solution", address: dbComponent.retailers.retailer_address || null, phone: dbComponent.retailers.retailer_phone || null, contactPerson: dbComponent.retailers.retailer_contact_person || null, email: dbComponent.retailers.email || null, website: dbComponent.retailers.website || null, } : undefined; const appComponent: Component = { id: `component-${dbComponent.component_id}`, name: dbComponent.component_name, brand: brand, price: Number(dbComponent.component_price) || 0, category: appCategory, image: `/placeholder.svg`, rating: 4.5, reviews: Math.floor(Math.random() * 1000) + 100, specifications: { Price: `₱${dbComponent.component_price || 0}`, Category: dbComponent.component_categories?.category_name || "Unknown", Retailer: dbComponent.retailers?.retailer_name || "Central Juan Solution", ...(appCategory === "memory" && compatInfo.type && { type: compatInfo.type }), ...(compatInfo.tdp && { TDP: `${compatInfo.tdp}W` }), ...(compatInfo.wattage && { Wattage: `${compatInfo.wattage}W` }), ...(compatInfo.vram && { VRAM: compatInfo.vram }), ...(compatInfo.capacity && { Capacity: compatInfo.capacity }), ...(compatInfo.speed && { Speed: compatInfo.speed }), ...(compatInfo.interface && { Interface: compatInfo.interface }), ...(appCategory === "case" && compatInfo.maxGpuLength && { maxGpuLength: compatInfo.maxGpuLength.toString(), }), ...(appCategory === "case" && compatInfo.maxCoolerHeight && { maxCoolerHeight: compatInfo.maxCoolerHeight.toString(), }), ...(appCategory === "cooling" && compatInfo.supportedSockets && { supportedSockets: JSON.stringify( Array.isArray(compatInfo.supportedSockets) ? compatInfo.supportedSockets : [compatInfo.supportedSockets], ), }), ...(appCategory === "cooling" && compatInfo.supported_sockets && { supportedSockets: JSON.stringify( Array.isArray(compatInfo.supported_sockets) ? compatInfo.supported_sockets : [compatInfo.supported_sockets], ), }), }, compatibility, performanceTags: performanceTags, availabilityStatus: dbComponent.availability_status || "in_stock", retailer: retailer, }; // Only set if category slot is empty (first component of that category) if (!importedComponents[appCategory]) { importedComponents[appCategory] = appComponent; } } // Set imported components setSelectedComponents(importedComponents); // Add imported components to the components list if they're not already there // This ensures they're available for compatibility checking and filtering setComponents((prevComponents) => { const existingIds = new Set(prevComponents.map((c) => c.id)); const newComponents = Object.values(importedComponents).filter( (comp): comp is Component => comp !== null && !existingIds.has(comp.id), ); return [...prevComponents, ...newComponents]; }); setBuildName(build.build_name || "Imported Build"); setBuildDescription(build.description || ""); if (build.build_type_id) { setBuildType(build.build_type_id.toString()); } // Close dialog and show success (unless silent e.g. for edit mode) if (!options?.silent) { setShowImportDialog(false); setImportSearchTerm(""); swal.success("Build imported!", `"${build.build_name}" imported successfully.`); } } catch (error) { console.error("Error importing build:", error); if (!options?.silent) { swal.error("Import failed", "Failed to import build. Please try again."); } throw error; } }; const handleViewSimilar = (buildId: string) => console.log("Viewing similar build:", buildId); const handleEditSimilar = (buildId: string) => console.log("Editing similar build:", buildId); const compatibilityResult = getCompatibilityResult(); const recommendations = new CompatibilityChecker( selectedComponents, ).getRecommendations(); useEffect(() => { const fetchComponents = async () => { setIsLoadingComponents(true); try { // Fetch components from Supabase database only const dbComponents = await getSupabaseComponents(); if (dbComponents.length > 0) { setComponents(dbComponents); console.log( `✅ Loaded ${dbComponents.length} components from Supabase database`, ); } else { // No components in database - show error message console.warn("⚠️ No components found in database"); setFetchError( "No components available. Please add components to the database.", ); setComponents([]); } } catch (err) { console.error("❌ Error fetching components from database:", err); setFetchError( "Failed to fetch components from database. Please check your connection.", ); setComponents([]); } finally { setIsLoadingComponents(false); } }; fetchComponents(); }, []); // Reset page when search term, filters, or category changes useEffect(() => { setComponentPage(0); }, [ searchTerm, locationFilter, performanceCategory, activeCategory, showOnlyCompatible, sortBy, ]); // Show wizard on first visit useEffect(() => { const hasSeenWizard = typeof window !== "undefined" && localStorage.getItem("buildmate-wizard-seen") === "true"; if (!hasSeenWizard) { setShowBuildWizard(true); } }, []); // Handle wizard completion const handleWizardComplete = (data: { priorityComponent: "gpu" | "cpu" | "none"; budget: number; performanceCategory: PerformanceCategory; experienceLevel: "beginner" | "intermediate" | "advanced"; }) => { // Set performance category setPerformanceCategory(data.performanceCategory); // Set budget if provided if (data.budget > 0) { setBudget(data.budget); setBudgetEnabled(true); } // Set experience level setUserExperienceLevel(data.experienceLevel); // Navigate to priority component if selected if (data.priorityComponent === "gpu") { setActiveCategory("gpu"); } else if (data.priorityComponent === "cpu") { setActiveCategory("cpu"); } // Mark wizard as seen if (typeof window !== "undefined") { localStorage.setItem("buildmate-wizard-seen", "true"); } setShowBuildWizard(false); }; const handleWizardSkip = () => { if (typeof window !== "undefined") { localStorage.setItem("buildmate-wizard-seen", "true"); } setShowBuildWizard(false); }; // Print build summary const handlePrintBuildSummary = () => { const printWindow = window.open("", "_blank"); if (!printWindow) return; const buildSummary = generateBuildSummaryHTML(); printWindow.document.write(buildSummary); printWindow.document.close(); printWindow.focus(); printWindow.print(); printWindow.close(); }; // Generate build summary as plain text const generateBuildSummaryText = () => { const date = new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); let summary = `BuildMate - PC Build Summary\n`; summary += `Generated: ${date}\n`; summary += `Build Name: ${buildName}\n`; summary += `Build Type: ${buildType === "1" ? "Academic" : buildType === "3" ? "Gaming" : "Custom"}\n`; summary += `\n${"=".repeat(50)}\n\n`; summary += `COMPONENTS:\n`; summary += `${"=".repeat(50)}\n\n`; Object.entries(selectedComponents).forEach(([category, component]) => { const categoryName = categoryNames[category as ComponentCategory]; summary += `${categoryName}:\n`; if (component) { summary += ` - ${component.name}\n`; summary += ` - Price: ${formatCurrency(component.price)}\n`; if (component.retailer) { summary += ` - Retailer: ${component.retailer.name}\n`; if (component.retailer.address) { summary += ` - Address: ${component.retailer.address}\n`; } } } else { summary += ` - Not selected\n`; } summary += `\n`; }); summary += `${"=".repeat(50)}\n\n`; summary += `TOTAL PRICE: ${formatCurrency(totalPrice)}\n\n`; if (budgetEnabled && budget > 0) { summary += `Budget: ${formatCurrency(budget)}\n`; summary += `Remaining: ${formatCurrency(remainingBudget)}\n`; summary += `Budget Usage: ${Math.round(budgetPercentage)}%\n\n`; } summary += `COMPATIBILITY:\n`; summary += `${"=".repeat(50)}\n`; summary += `Score: ${compatibilityResult.score}%\n`; if (compatibilityResult.issues.length > 0) { summary += `\nIssues:\n`; compatibilityResult.issues.forEach((issue, index) => { summary += `${index + 1}. [${issue.type.toUpperCase()}] ${issue.message}\n`; if (issue.suggestion) { summary += ` Suggestion: ${issue.suggestion}\n`; } }); } else { summary += `\nNo compatibility issues detected.\n`; } summary += `\n${"=".repeat(50)}\n`; summary += `\nGenerated by BuildMate - Your PC Building Companion\n`; summary += `Visit: ${typeof window !== "undefined" ? window.location.origin : "https://buildmate.com"}\n`; return summary; }; // Generate build summary as HTML for printing const generateBuildSummaryHTML = () => { const date = new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); let html = ` BuildMate - Build Summary

BuildMate - PC Build Summary

Generated: ${date}

Build Name: ${buildName}

Build Type: ${buildType === "1" ? "Academic" : buildType === "3" ? "Gaming" : "Custom"}

Components

`; Object.entries(selectedComponents).forEach(([category, component]) => { const categoryName = categoryNames[category as ComponentCategory]; html += ``; }); html += ``; if (budgetEnabled && budget > 0) { html += ``; } html += `
Category Component Price Retailer
${categoryName} ${component ? component.name : "Not selected"} ${component ? formatCurrency(component.price) : formatCurrency(0)} ${component?.retailer?.name || "N/A"}
Total Price ${formatCurrency(totalPrice)}
Budget ${formatCurrency(budget)}
Remaining ${formatCurrency(remainingBudget)}
Budget Usage ${Math.round(budgetPercentage)}%

Compatibility

Compatibility Score: ${compatibilityResult.score}%

`; if (compatibilityResult.issues.length > 0) { html += `

Issues:

`; compatibilityResult.issues.forEach((issue) => { html += `
[${issue.type.toUpperCase()}] ${issue.message} ${issue.suggestion ? `
Suggestion: ${issue.suggestion}` : ""}
`; }); } else { html += `

✅ No compatibility issues detected.

`; } html += ` `; return html; }; return (
{/* Build Wizard */} {/* Dialogs - Preview Purchase, Save, Success, etc. */} Purchase Details Preview Review your build components and purchase information before saving
{/* Build Summary */} {buildName} Build Type:{" "} {buildType === "1" ? "Academic" : buildType === "3" ? "Gaming" : "Custom"}
{Object.entries(selectedComponents) .filter(([_, component]) => component !== null) .map(([category, component]) => { const categoryName = categoryNames[category as ComponentCategory]; const Icon = categoryIcons[category as ComponentCategory]; return (

{component!.name}

{categoryName} {component!.brand && (

Brand: {component!.brand}

)} {component!.retailer && (

Retailer: {component!.retailer.name} {component!.retailer.address && ` • ${component!.retailer.address.split(",")[0]}`}

)}

{formatCurrency(component!.price)}

{component!.specifications && Object.keys(component!.specifications).length > 0 && (

Specifications:

{Object.entries(component!.specifications) .filter(([key]) => key !== "Compatibility") .slice(0, 3) .map(([key, value]) => (

{key}: {" "} {String(value)}

))}
)} {component!.retailer && (
)}
); })}
{/* Order Summary */} Order Summary
Components ( {Object.values(selectedComponents).filter(Boolean).length} ) {formatCurrency(totalPrice)}
Total {formatCurrency(totalPrice)}

Save your build first to access full purchase features and send purchase details to retailers

Save Your Build Give your build a name to save it to your profile
setBuildName(e.target.value)} placeholder="Enter build name" />