"use client" import React, { useState, useEffect } from "react" import { use } from "react" import Link from "next/link" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Separator } from "@/components/ui/separator" import { MapPin, Share, LinkIcon, Calendar, Eye, Cpu, Monitor, Heart, MessageCircle, User } from "lucide-react" import { supabase } from "@/lib/supabase" import { Button } from "@/components/ui/button" import { formatCurrency } from "@/lib/currency" import { useAuth } from "@/contexts/supabase-auth-context" import { swal } from "@/lib/sweetalert" interface ProfilePageProps { params: Promise<{ id: string }> } export default function ProfilePage({ params }: ProfilePageProps) { const { id: userId } = use(params) const [isFollowing, setIsFollowing] = useState(false) const [userProfile, setUserProfile] = useState(null) const [userBuilds, setUserBuilds] = useState([]) const [followers, setFollowers] = useState(0) const [following, setFollowing] = useState(0) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const { user } = useAuth() const [sharePopup, setSharePopup] = useState(null) const [likedBuilds, setLikedBuilds] = useState([]) const [likeOwnPopup, setLikeOwnPopup] = useState(false) useEffect(() => { if (!user) return const fetchLikedBuilds = async () => { const { data, error } = await supabase .from("build_likes") .select("build_id") .eq("user_id", user.user_id) if (!error && data) { setLikedBuilds(data.map((row) => row.build_id)) } } fetchLikedBuilds() }, [user]) useEffect(() => { if (!userBuilds || userBuilds.length === 0) return; const fetchViews = async () => { const buildIds = userBuilds.map((b) => b.build_id); const { data: viewsData, error } = await supabase .from("build_views") .select("build_id, user_id", { count: "exact" }) // get all rows .in("build_id", buildIds); if (error) { console.error("Failed to fetch build views:", error); return; } // Count views per build const viewsMap: Record = {}; buildIds.forEach((id) => (viewsMap[id] = 0)); viewsData?.forEach((row: any) => { viewsMap[row.build_id] = (viewsMap[row.build_id] || 0) + 1; }); // Update builds with views setUserBuilds((prev) => prev.map((b) => ({ ...b, views: viewsMap[b.build_id] || 0, })) ); }; fetchViews(); }, [userBuilds]); useEffect(() => { if (!userBuilds || userBuilds.length === 0) return const fetchLikesCount = async () => { const buildIds = userBuilds.map((b) => b.build_id) const { data: likes, error } = await supabase .from("build_likes") .select("build_id") .in("build_id", buildIds) if (!error && likes) { const counts: Record = {} buildIds.forEach((id) => (counts[id] = 0)) likes.forEach((like: any) => { counts[like.build_id] = (counts[like.build_id] || 0) + 1 }) setUserBuilds((prev) => prev.map((b) => ({ ...b, likesCount: counts[b.build_id] || 0 })) ) } } fetchLikesCount() }, [userBuilds.length]) // run once after builds are fetched useEffect(() => { if (!userBuilds || userBuilds.length === 0) return; const fetchCommentsCount = async () => { const buildIds = userBuilds.map((b) => b.build_id); const { data: comments, error } = await supabase .from("build_comments") .select("build_id", { count: "exact" }) .in("build_id", buildIds); if (error) { console.error("Failed to fetch comments:", error); return; } const counts: Record = {}; buildIds.forEach((id) => (counts[id] = 0)); comments?.forEach((comment: any) => { counts[comment.build_id] = (counts[comment.build_id] || 0) + 1; }); setUserBuilds((prev) => prev.map((b) => ({ ...b, comments: counts[b.build_id] || 0 })) ); }; fetchCommentsCount(); }, [userBuilds.length]); // Check if already following useEffect(() => { if (!user?.user_id || user.user_id === Number(userId)) return const checkFollowing = async () => { const { data } = await supabase .from("followers") .select("*") .eq("user_id", userId) .eq("follower_user_id", user?.user_id) .single() setIsFollowing(!!data) } checkFollowing() }, [user?.user_id, userId]) // Follow/unfollow handler const handleFollow = async () => { if (!user?.user_id) return; // 🚫 Prevent self-follow if (user.user_id === Number(userId)) { return; } try { if (isFollowing) { // Unfollow const { error: unfollowError } = await supabase .from("followers") .delete() .eq("user_id", userId) .eq("follower_user_id", user.user_id); if (unfollowError) { console.error("Failed to unfollow:", unfollowError); return; } setIsFollowing(false); setFollowers((prev) => Math.max(prev - 1, 0)); } else { // Follow const { error: followError } = await supabase .from("followers") .insert({ user_id: userId, follower_user_id: user.user_id, created_at: new Date().toISOString(), }); if (followError) { console.error("Failed to follow:", followError); return; } setIsFollowing(true); setFollowers((prev) => prev + 1); // Create follow notification const { error: notifError } = await supabase .from("notifications") .insert({ user_id: userId, actor_user_id: user.user_id, type: "follow", }); if (!notifError) { // Send push notification to the followed user (non-blocking) try { const payload = { userId: userId, notificationType: 'followers', title: `${user.user_name} started following you`, body: `${user.user_name} is now following you`, url: `/profile/${user.user_id}`, }; console.log('Sending push notification for new follower (from profile):', payload); const res = await fetch('/api/notifications/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const sendResult = await res.json(); console.log('Push send result for follower (from profile):', sendResult); } catch (err) { console.error('Error sending push notification for follower:', err); } } } } catch (err) { console.error("Follow action failed:", err); } }; // Fetch profile, builds, followers/following useEffect(() => { const fetchData = async () => { try { setLoading(true) setError(null) const { data: profile, error: profileError } = await supabase .from("users") .select("*") .eq("user_id", userId) .single() if (profileError || !profile) { setError("User not found") return } // load privacy settings from dedicated table, fallback to column let privacySettings: any = {} try { const { data: priv, error: privError } = await supabase .from('privacy_settings') .select('*') .eq('user_id', userId) .maybeSingle(); if (!privError && priv) { privacySettings = { profilePublic: priv.profile_public, showBuilds: priv.show_builds }; } else { privacySettings = typeof profile.privacy_settings === "string" ? JSON.parse(profile.privacy_settings) : profile.privacy_settings || {}; } } catch (e) { console.error('Error fetching privacy settings for profile page:', e); privacySettings = typeof profile.privacy_settings === "string" ? JSON.parse(profile.privacy_settings) : profile.privacy_settings || {}; } setUserProfile({ ...profile, privacy_settings: privacySettings, }) if (!privacySettings?.profilePublic) { setError("This profile is private") return } const { data: builds, error: buildsError } = await supabase .from("builds") .select("*") .eq("user_id", userId) if (!buildsError && builds) { setUserBuilds(builds) } const { count: followersCount } = await supabase .from("followers") .select("*", { count: "exact", head: true }) .eq("user_id", userId) setFollowers(followersCount || 0) const { count: followingCount } = await supabase .from("followers") .select("*", { count: "exact", head: true }) .eq("follower_user_id", userId) setFollowing(followingCount || 0) } catch (err: any) { console.error(err) setError("Failed to load user profile") } finally { setLoading(false) } } fetchData() }, [userId, user?.user_id]) if (loading) { return (

Loading profile...

) } if (error || !userProfile) { if (error === "This profile is private") { return (
🔒

This profile is private

The user has chosen to keep their profile hidden.

) } else { return (
{error || "Failed to load profile"}
) } } // Like build handler const handleLike = async (build: any) => { if (!user) { swal.info("Login required", "Please log in to like builds."); return } const isLiked = likedBuilds.includes(build.build_id) // prevent liking own build if (build.user_id === user.user_id) { setLikeOwnPopup(true) return } if (isLiked) { // UNLIKE const { error: deleteLikeError } = await supabase .from("build_likes") .delete() .eq("build_id", build.build_id) .eq("user_id", user.user_id) if (!deleteLikeError) { setLikedBuilds((prev) => prev.filter((id) => id !== build.build_id)) setUserBuilds((prev) => prev.map((b) => b.build_id === build.build_id ? { ...b, likesCount: Math.max((b.likesCount || 1) - 1, 0) } : b ) ) // remove notification await supabase .from("notifications") .delete() .eq("type", "like") .eq("build_id", build.build_id) .eq("actor_user_id", user.user_id) .eq("user_id", build.user_id) } } else { // LIKE const { error: insertLikeError } = await supabase .from("build_likes") .insert({ build_id: build.build_id, user_id: user.user_id, }) if (!insertLikeError) { setLikedBuilds((prev) => [...prev, build.build_id]) setUserBuilds((prev) => prev.map((b) => b.build_id === build.build_id ? { ...b, likesCount: (b.likesCount || 0) + 1 } : b ) ) // create notification if (user.user_id !== build.user_id) { await supabase.from("notifications").insert({ user_id: build.user_id, // owner actor_user_id: user.user_id, // liker type: "like", build_id: build.build_id, is_read: false, }) // Send push notification to the 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 (profile):', 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 (profile):', r)).catch(() => {})) .catch(err => console.error('Error sending push (profile):', err)); } catch (err) { console.error('Error preparing push send (profile):', err); } } } } } return (
{/* Profile Overview */}
{userProfile.avatar_url ? ( ) : ( {userProfile.user_name ? userProfile.user_name.charAt(0).toUpperCase() : "?"} )}
{userProfile.user_name}

{userProfile.email}

{userProfile.bio}

{userProfile.location}
Joined {new Date(userProfile.created_at).toLocaleDateString()}

{userBuilds?.length || 0}

Builds

{following}

Following

{followers}

Followers

{/* Follow Button */} {user?.user_id && user?.user_id !== Number(userId) && (
)}
{/* Builds Section */}
{/* Section Title */}

Builds

{/* Builds List */}
{userProfile?.privacy_settings?.showBuilds === false ? (

Builds are private

This user has hidden their builds.

) : (!userBuilds || userBuilds.length === 0) ? (

No builds yet

This user hasn’t shared any builds yet.

) : ( userBuilds.map((build, index) => { const cpuComponent = build.build_components?.find((bc: any) => bc.components?.component_categories?.category_name === "CPU" ) const isLiked = likedBuilds.includes(build.build_id) const gpuComponent = build.build_components?.find((bc: any) => bc.components?.component_categories?.category_name === "GPU" ) const totalPrice = Number(build.total_price) || 0; return (
{build.build_name}

{formatCurrency(totalPrice)}

{(build.views || 0).toLocaleString()} views

{build.description || 'PC Build'}

{cpuComponent && (
{cpuComponent.components?.component_name}
)} {gpuComponent && (
{gpuComponent.components?.component_name}
)}
) }) )}
{/* Share popup */} {sharePopup && (

Share Build

)} {/* Like Own Build Popup */} {likeOwnPopup && (

Oops!

You cannot like your own build.

)}
) }