diff --git a/client/pages/Profile.tsx b/client/pages/Profile.tsx index a007d80d..8ce06828 100644 --- a/client/pages/Profile.tsx +++ b/client/pages/Profile.tsx @@ -1,10 +1,19 @@ -import { useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useEffect, useMemo, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; import Layout from "@/components/Layout"; -import { Button } from "@/components/ui/button"; +import LoadingScreen from "@/components/LoadingScreen"; import { useAuth } from "@/contexts/AuthContext"; -import { useAethexToast } from "@/hooks/use-aethex-toast"; -import { supabase } from "@/lib/supabase"; +import { + aethexAchievementService, + type AethexAchievement, +} from "@/lib/aethex-database-adapter"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, @@ -12,836 +21,357 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Switch } from "@/components/ui/switch"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Separator } from "@/components/ui/separator"; -import LoadingScreen from "@/components/LoadingScreen"; import { - User, - Settings, - Activity, - Globe, - Shield, - Bell, - Database, - Monitor, - Network, - Zap, - TrendingUp, - Users, - Server, - Eye, - Link2, - Wifi, - Cloud, - Lock, - ChevronRight, - RefreshCw, - AlertTriangle, - CheckCircle, - XCircle, - Circle, + CalendarClock, + Compass, + Edit, + MapPin, + Rocket, + ShieldStar, + Trophy, + UserCircle, } from "lucide-react"; -interface SiteStatus { - name: string; - url: string; - status: "online" | "offline" | "maintenance"; - lastCheck: string; - responseTime: number; - version: string; +interface ProfileStat { + label: string; + value: string; + helper?: string; + Icon: typeof UserCircle; } -interface CrossSiteCommunication { - siteName: string; - lastSync: string; - dataExchanged: number; - status: "connected" | "disconnected" | "syncing"; -} +const safeRelativeDate = (value?: string | null) => { + if (!value) return null; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return null; + const diff = Date.now() - date.getTime(); + const minutes = Math.round(diff / (1000 * 60)); + if (minutes < 1) return "just now"; + if (minutes < 60) return `${minutes} min ago`; + const hours = Math.round(minutes / 60); + if (hours < 24) return `${hours} hr ago`; + const days = Math.round(hours / 24); + if (days < 7) return `${days} day${days === 1 ? "" : "s"} ago`; + const weeks = Math.round(days / 7); + if (weeks < 5) return `${weeks} wk${weeks === 1 ? "" : "s"} ago`; + const months = Math.round(days / 30.4375); + if (months < 12) return `${months} mo${months === 1 ? "" : "s"} ago`; + const years = Math.round(days / 365.25); + return `${years} yr${years === 1 ? "" : "s"} ago`; +}; -export default function Profile() { +const Profile = () => { const navigate = useNavigate(); - const { user, profile, loading: authLoading, updateProfile } = useAuth(); - const { success: toastSuccess, error: toastError } = useAethexToast(); - const [isLoading, setIsLoading] = useState(true); - const [isSaving, setIsSaving] = useState(false); + const { user, profile, loading: authLoading } = useAuth(); + const [achievements, setAchievements] = useState([]); + const [loadingAchievements, setLoadingAchievements] = useState(false); - // Profile settings state - const [profileData, setProfileData] = useState({ - displayName: "", - bio: "", - company: "", - location: "", - website: "", - githubUsername: "", - twitterUsername: "", - linkedinUrl: "", - }); - - // Notification settings - const [notifications, setNotifications] = useState({ - emailNotifications: true, - pushNotifications: true, - projectUpdates: true, - marketingEmails: false, - securityAlerts: true, - }); - - // Privacy settings - const [privacy, setPrivacy] = useState({ - profileVisibility: "public", - showEmail: false, - showProjects: true, - allowAnalytics: true, - }); - - // Overseer dashboard data - const [overseerData, setOverseerData] = useState({ - systemHealth: 98.5, - activeUsers: 1247, - totalProjects: 3592, - serverLoad: 23, - dataProcessed: 15.7, // GB - apiCalls: 45231, - errorRate: 0.03, - }); - - // Cross-site communication data - const [crossSiteData, setCrossSiteData] = useState([ - { - siteName: "AeThex Labs", - lastSync: "2 minutes ago", - dataExchanged: 1.2, - status: "connected", - }, - { - siteName: "Development Portal", - lastSync: "5 minutes ago", - dataExchanged: 0.8, - status: "syncing", - }, - { - siteName: "Community Hub", - lastSync: "12 minutes ago", - dataExchanged: 2.1, - status: "connected", - }, - ]); - - // Site monitoring data - const [siteStatuses, setSiteStatuses] = useState([ - { - name: "Main Site", - url: "https://core.aethex.biz", - status: "online", - lastCheck: "Just now", - responseTime: 245, - version: "1.0.0", - }, - { - name: "API Gateway", - url: "https://api.aethex.biz", - status: "online", - lastCheck: "30 seconds ago", - responseTime: 123, - version: "2.1.3", - }, - { - name: "Labs Portal", - url: "https://labs.aethex.biz", - status: "maintenance", - lastCheck: "2 minutes ago", - responseTime: 0, - version: "1.5.2", - }, - ]); - - const currentStreak = profile?.current_streak ?? 0; - const longestStreak = profile?.longest_streak ?? currentStreak; + const username = profile?.username || user?.email?.split("@")[0] || "creator"; + const passportHref = `/passport/${encodeURIComponent(username)}`; + const dashboardSettingsHref = "/dashboard?tab=profile#settings"; useEffect(() => { if (!authLoading && !user) { - navigate("/login"); - return; + navigate("/login", { replace: true }); } + }, [authLoading, user, navigate]); - if (profile) { - setProfileData({ - displayName: profile.full_name || "", - bio: profile.bio || "", - company: (profile as any).company || "", - location: profile.location || "", - website: (profile as any).website || "", - githubUsername: (profile as any).github_username || "", - twitterUsername: (profile as any).twitter_username || "", - linkedinUrl: profile.linkedin_url || "", - }); - } - - setIsLoading(false); - }, [authLoading, user, profile, navigate]); - - const handleProfileSave = async () => { - if (!user) return; - - setIsSaving(true); - console.log("Starting profile save...", { - user: user.id, - profile: !!profile, - profileData, - }); - - try { - // Check if profile exists, create if it doesn't - if (!profile) { - console.log("No profile exists, creating one..."); - - try { - // Try using the AuthContext updateProfile (which works with mock too) - await updateProfile({ - username: - profileData.displayName || - user.email?.split("@")[0] || - `user_${Date.now()}`, - full_name: profileData.displayName, - bio: profileData.bio, - location: profileData.location, - linkedin_url: profileData.linkedinUrl, - github_url: profileData.githubUsername - ? `https://github.com/${profileData.githubUsername}` - : null, - twitter_url: profileData.twitterUsername - ? `https://twitter.com/${profileData.twitterUsername}` - : null, - user_type: "game_developer", - experience_level: "beginner", - level: 1, - total_xp: 0, - } as any); - - console.log("Profile created successfully via AuthContext"); - } catch (authError) { - console.warn( - "AuthContext creation failed, trying direct database:", - authError, - ); - - // Fallback to direct database call - const { data, error } = await supabase - .from("user_profiles") - .insert({ - id: user.id, - username: - profileData.displayName || - user.email?.split("@")[0] || - `user_${Date.now()}`, - user_type: "game_developer", - experience_level: "beginner", - full_name: profileData.displayName, - bio: profileData.bio, - location: profileData.location, - linkedin_url: profileData.linkedinUrl, - github_url: profileData.githubUsername - ? `https://github.com/${profileData.githubUsername}` - : null, - twitter_url: profileData.twitterUsername - ? `https://twitter.com/${profileData.twitterUsername}` - : null, - level: 1, - total_xp: 0, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }) - .select() - .single(); - - if (error) { - console.error("Error creating profile:", error); - throw error; - } - - console.log("Profile created successfully via direct DB:", data); - } - } else { - console.log("Updating existing profile..."); - await updateProfile({ - full_name: profileData.displayName, - bio: profileData.bio, - location: profileData.location, - linkedin_url: profileData.linkedinUrl, - github_url: profileData.githubUsername - ? `https://github.com/${profileData.githubUsername}` - : null, - twitter_url: profileData.twitterUsername - ? `https://twitter.com/${profileData.twitterUsername}` - : null, - updated_at: new Date().toISOString(), - } as any); + useEffect(() => { + const loadAchievements = async () => { + if (!user?.id) return; + setLoadingAchievements(true); + try { + const data = await aethexAchievementService.getUserAchievements(user.id); + setAchievements(data.slice(0, 6)); + } catch (error) { + console.warn("Failed to load achievements for profile overview", error); + setAchievements([]); + } finally { + setLoadingAchievements(false); } + }; - toastSuccess({ - title: "Profile Updated", - description: "Your profile has been successfully updated.", - }); - } catch (error: any) { - console.error("Profile save error:", error); - toastError({ - title: "Update Failed", - description: - error.message || "Failed to update profile. Please try again.", - }); - } finally { - setIsSaving(false); - } - }; + loadAchievements().catch(() => undefined); + }, [user?.id]); - const getStatusIcon = (status: string) => { - switch (status) { - case "online": - case "connected": - return ; - case "offline": - case "disconnected": - return ; - case "maintenance": - case "syncing": - return ; - default: - return ; - } - }; + const stats = useMemo(() => { + const level = Math.max(1, Number(profile?.level ?? 1)); + const totalXp = Math.max(0, Number(profile?.total_xp ?? 0)); + const loyalty = Math.max(0, Number((profile as any)?.loyalty_points ?? 0)); + const streak = Math.max(0, Number(profile?.current_streak ?? 0)); - const getStatusColor = (status: string) => { - switch (status) { - case "online": - case "connected": - return "bg-green-500"; - case "offline": - case "disconnected": - return "bg-red-500"; - case "maintenance": - case "syncing": - return "bg-yellow-500"; - default: - return "bg-gray-400"; - } - }; + return [ + { + label: "Level", + value: `Lv ${level}`, + helper: "Progress toward next milestone", + Icon: ShieldStar, + }, + { + label: "Total XP", + value: `${totalXp.toLocaleString()} XP`, + helper: "Earned across AeThex activities", + Icon: Rocket, + }, + { + label: "Loyalty", + value: loyalty.toLocaleString(), + helper: "Reward points available", + Icon: Trophy, + }, + { + label: "Streak", + value: `${streak} day${streak === 1 ? "" : "s"}`, + helper: "Keep shipping to extend your streak", + Icon: CalendarClock, + }, + ]; + }, [profile]); - if (authLoading || isLoading) { - return ; + const socialLinks = useMemo(() => { + const entries: { label: string; url: string; domain: string }[] = []; + const maybePush = (label: string, value?: string | null) => { + if (!value) return; + const trimmed = value.trim(); + if (!trimmed) return; + const url = /^https?:/i.test(trimmed) ? trimmed : `https://${trimmed}`; + try { + const u = new URL(url); + entries.push({ label, url: u.toString(), domain: u.host }); + } catch (error) { + console.warn(`Skipping invalid ${label} link`, value, error); + } + }; + + maybePush("Website", (profile as any)?.website_url); + maybePush("GitHub", profile?.github_url); + maybePush("LinkedIn", profile?.linkedin_url); + maybePush("Twitter", profile?.twitter_url); + + return entries; + }, [profile]); + + if (authLoading || !user || !profile) { + return ; } + const lastUpdated = safeRelativeDate(profile.updated_at); + const memberSince = safeRelativeDate(profile.created_at); + return ( -
-
- {/* Header */} -
-
- - - - {profile?.full_name?.[0] || user?.email?.[0]?.toUpperCase()} - - -
-

- {profile?.full_name || "User Profile"} -

-

{user?.email}

-
- - Current streak: {currentStreak}d - - - Longest streak: {longestStreak}d - +
+
+
+ + + + + + {(profile.full_name || username) + .split(" ") + .map((name) => name[0]) + .join("") + .toUpperCase() + .slice(0, 2)} + + +
+ + {profile.full_name || username} + + + {profile.user_type ? ( + + {profile.user_type.replace(/_/g, " ")} + + ) : null} + {profile.experience_level ? ( + + {profile.experience_level} + + ) : null} + {profile.location ? ( + + + {profile.location} + + ) : null} +
-
-
-
- - - - - - Profile - - - - Settings - - - - Overseer - - - - Network - - - - {/* Profile Settings Tab */} - - - - - - Profile Information - - - Update your profile information and social links. - - - -
-
- - - setProfileData({ - ...profileData, - displayName: e.target.value, - }) - } - className="bg-slate-900/50 border-slate-600 text-white" - /> -
-
- - - setProfileData({ - ...profileData, - company: e.target.value, - }) - } - className="bg-slate-900/50 border-slate-600 text-white" - /> -
-
- - - setProfileData({ - ...profileData, - location: e.target.value, - }) - } - className="bg-slate-900/50 border-slate-600 text-white" - /> -
-
- - - setProfileData({ - ...profileData, - website: e.target.value, - }) - } - className="bg-slate-900/50 border-slate-600 text-white" - /> -
-
-
- -