From e5f7b7fbf4ae7539de42e00dc86a31afecfd7583 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 08:27:40 +0000 Subject: [PATCH] Redesigned Dashboard with Arm-Centric Layout and Improved UI Stability cgen-bf516ddb20f44a0a892d89ce95c959d1 --- client/pages/Dashboard.tsx | 2275 ++++++------------------------------ 1 file changed, 359 insertions(+), 1916 deletions(-) diff --git a/client/pages/Dashboard.tsx b/client/pages/Dashboard.tsx index e0f1407e..35ed67f1 100644 --- a/client/pages/Dashboard.tsx +++ b/client/pages/Dashboard.tsx @@ -1,27 +1,9 @@ import { useState, useEffect, useMemo } from "react"; - -const API_BASE = import.meta.env.VITE_API_BASE || ""; - import { useNavigate, useSearchParams } from "react-router-dom"; import Layout from "@/components/Layout"; import { Button } from "@/components/ui/button"; import { useAuth } from "@/contexts/AuthContext"; import { aethexToast } from "@/lib/aethex-toast"; -import { supabase } from "@/lib/supabase"; -import { - aethexProjectService, - aethexAchievementService, -} from "@/lib/aethex-database-adapter"; -import { communityService } from "@/lib/supabase-service"; -import { aethexCollabService } from "@/lib/aethex-collab-service"; -import { aethexSocialService } from "@/lib/aethex-social-service"; -import PostComposer from "@/components/social/PostComposer"; -import OAuthConnections, { - ProviderDescriptor, - ProviderKey, -} from "@/components/settings/OAuthConnections"; -import WalletVerification from "@/components/settings/WalletVerification"; -import RealmSwitcher, { RealmKey } from "@/components/settings/RealmSwitcher"; import { Card, CardContent, @@ -33,20 +15,16 @@ 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 { Separator } from "@/components/ui/separator"; import LoadingScreen from "@/components/LoadingScreen"; import { Link } from "react-router-dom"; +import OAuthConnections from "@/components/settings/OAuthConnections"; +import RealmSwitcher, { RealmKey } from "@/components/settings/RealmSwitcher"; import { User, Settings, - Activity, TrendingUp, - Zap, - Target, Users, - Calendar, Bell, Star, Trophy, @@ -55,1983 +33,448 @@ import { Database, Shield, ChevronRight, - MoreHorizontal, - Play, - Pause, - BarChart3, - Github, - Globe, + Activity, + LogOut, + Sparkles, CheckCircle, + ArrowRight, } from "lucide-react"; -const DiscordIcon = () => ( - - - -); +const ARMS = [ + { + id: "staff", + label: "Staff", + color: "#7c3aed", + href: "/staff", + icon: Shield, + description: "Operations & Administration", + bgGradient: "from-purple-500/20 to-purple-900/20", + borderColor: "border-purple-500/30 hover:border-purple-500/60", + }, + { + id: "labs", + label: "Labs", + color: "#FBBF24", + href: "/labs", + icon: Code, + description: "Research & Development", + bgGradient: "from-amber-500/20 to-amber-900/20", + borderColor: "border-amber-500/30 hover:border-amber-500/60", + }, + { + id: "gameforge", + label: "GameForge", + color: "#22C55E", + href: "/gameforge", + icon: Rocket, + description: "Game Development", + bgGradient: "from-green-500/20 to-green-900/20", + borderColor: "border-green-500/30 hover:border-green-500/60", + }, + { + id: "corp", + label: "Corp", + color: "#3B82F6", + href: "/corp", + icon: Users, + description: "Business & Consulting", + bgGradient: "from-blue-500/20 to-blue-900/20", + borderColor: "border-blue-500/30 hover:border-blue-500/60", + }, + { + id: "foundation", + label: "Foundation", + color: "#EF4444", + href: "/foundation", + icon: Trophy, + description: "Education & Mentorship", + bgGradient: "from-red-500/20 to-red-900/20", + borderColor: "border-red-500/30 hover:border-red-500/60", + }, + { + id: "devlink", + label: "Dev-Link", + color: "#06B6D4", + href: "/dev-link", + icon: Database, + description: "Developer Network", + bgGradient: "from-cyan-500/20 to-cyan-900/20", + borderColor: "border-cyan-500/30 hover:border-cyan-500/60", + }, + { + id: "nexus", + label: "Nexus", + color: "#A855F7", + href: "/nexus", + icon: Sparkles, + description: "Talent Marketplace", + bgGradient: "from-purple-500/20 to-fuchsia-900/20", + borderColor: "border-purple-500/30 hover:border-purple-500/60", + }, +]; export default function Dashboard() { const navigate = useNavigate(); - const { - user, - profile, - loading: authLoading, - updateProfile, - profileComplete, - linkedProviders, - linkProvider, - unlinkProvider, - } = useAuth(); + const { user, profile, loading: authLoading, signOut, profileComplete, linkedProviders, linkProvider, unlinkProvider } = useAuth(); + const [searchParams] = useSearchParams(); + const [activeTab, setActiveTab] = useState(() => searchParams.get("tab") ?? "realms"); const [displayName, setDisplayName] = useState(""); - const [locationInput, setLocationInput] = useState(""); const [bio, setBio] = useState(""); const [website, setWebsite] = useState(""); const [linkedin, setLinkedin] = useState(""); const [github, setGithub] = useState(""); const [twitter, setTwitter] = useState(""); const [savingProfile, setSavingProfile] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [projects, setProjects] = useState([]); - const [teams, setTeams] = useState([]); - const [invites, setInvites] = useState([]); - const [achievements, setAchievements] = useState([]); // earned achievements - const [allAchievements, setAllAchievements] = useState([]); - const [achievementFilter, setAchievementFilter] = useState< - "all" | "earned" | "locked" - >("earned"); - const [followingIds, setFollowingIds] = useState([]); - const [followerIds, setFollowerIds] = useState([]); - const [connectionsList, setConnectionsList] = useState([]); - const [profileCompletion, setProfileCompletion] = useState(0); - const [stats, setStats] = useState({ - activeProjects: 0, - completedTasks: 0, - teamMembers: 0, - performanceScore: "0%", - }); - const [userPosts, setUserPosts] = useState([]); - const [applications, setApplications] = useState([]); - const [connectionAction, setConnectionAction] = useState(null); - const [userRealm, setUserRealm] = useState(null); - const [experienceLevel, setExperienceLevel] = useState("beginner"); - const [savingRealm, setSavingRealm] = useState(false); - const [searchParams, setSearchParams] = useSearchParams(); - const [activeTab, setActiveTab] = useState( - () => searchParams.get("tab") ?? "profile", - ); - - const currentStreak = profile?.current_streak ?? 0; - const longestStreak = profile?.longest_streak ?? currentStreak; - const streakLabel = `${currentStreak} day${currentStreak === 1 ? "" : "s"} streak`; - - const linkedProviderMap = useMemo(() => { - const map: Record = {}; - linkedProviders.forEach((lp) => { - map[lp.provider] = lp; - }); - return map; - }, [linkedProviders]); useEffect(() => { - const paramTab = searchParams.get("tab") ?? "profile"; - if (paramTab !== activeTab) { - setActiveTab(paramTab); + if (profile) { + setDisplayName(profile.full_name || ""); + setBio(profile.bio || ""); + setWebsite(profile.website_url || ""); + setLinkedin(profile.linkedin_url || ""); + setGithub(profile.github_url || ""); + setTwitter(profile.twitter_url || ""); } - }, [searchParams, activeTab]); - - // Accept ?realm= to switch dashboards via Realms page - useEffect(() => { - const paramRealm = (searchParams.get("realm") || "").trim() as RealmKey; - const validRealms: RealmKey[] = [ - "game_developer", - "client", - "community_member", - "customer", - "staff", - ]; - const current = ((profile as any)?.user_type as RealmKey) ?? null; - if ( - paramRealm && - validRealms.includes(paramRealm) && - paramRealm !== current - ) { - (async () => { - try { - await updateProfile({ user_type: paramRealm } as any); - setUserRealm(paramRealm); - } catch {} - // remove query param after applying - const next = new URLSearchParams(searchParams.toString()); - next.delete("realm"); - setSearchParams(next, { replace: true }); - })(); - } else if (paramRealm) { - const next = new URLSearchParams(searchParams.toString()); - next.delete("realm"); - setSearchParams(next, { replace: true }); - } - }, [searchParams, profile?.user_type, updateProfile, setSearchParams]); - - // Prevent infinite loading state - timeout after 10s - useEffect(() => { - if (isLoading) { - const timeout = setTimeout(() => { - console.warn("Dashboard loading timeout - forcing completion"); - setIsLoading(false); - }, 10000); - return () => clearTimeout(timeout); - } - }, [isLoading]); - - useEffect(() => { - if (typeof window === "undefined" || !user) { - return; - } - const sanitized = new URLSearchParams(window.location.search); - const keysToStrip = [ - "code", - "state", - "scope", - "auth_error", - "error_description", - "access_token", - "refresh_token", - "token_type", - "provider", - "type", - ]; - let mutated = false; - keysToStrip.forEach((key) => { - if (sanitized.has(key)) { - sanitized.delete(key); - mutated = true; - } - }); - - if (mutated) { - setSearchParams(sanitized, { replace: true }); - } - }, [user?.id, setSearchParams]); - - const handleTabChange = (value: string) => { - console.log("[Dashboard] Tab change:", value); - setActiveTab(value); - const next = new URLSearchParams(searchParams.toString()); - if (value === "profile") { - if (next.has("tab")) { - next.delete("tab"); - setSearchParams(next, { replace: true }); - } - return; - } - - if (next.get("tab") !== value) { - next.set("tab", value); - setSearchParams(next, { replace: true }); - } - }; - - const handleRealmSave = async () => { - if (!user) { - aethexToast.error({ - title: "Not signed in", - description: "Sign in to update your realm preferences.", - }); - return; - } - - if (!userRealm) { - aethexToast.error({ - title: "Select a realm", - description: "Choose a realm before saving your preferences.", - }); - return; - } - - setSavingRealm(true); - const payload = { - user_type: userRealm, - experience_level: experienceLevel || "beginner", - } as any; - - try { - await updateProfile(payload); - computeProfileCompletion({ - ...(profile as any), - ...payload, - }); - aethexToast.success({ - title: "Realm updated", - description: "Your AeThex experience has been refreshed.", - }); - } catch (error: any) { - console.error("Failed to save realm:", error); - aethexToast.error({ - title: "Unable to save realm", - description: error?.message || "Please try again or refresh the page.", - }); - } finally { - setSavingRealm(false); - } - }; - - const oauthConnections = useMemo( - () => [ - { - provider: "google", - name: "Google", - description: "Link your Google account for one-click access.", - Icon: Globe, - gradient: "from-red-500 to-yellow-500", - }, - { - provider: "github", - name: "GitHub", - description: "Connect your GitHub account to sync contributions.", - Icon: Github, - gradient: "from-gray-600 to-gray-900", - }, - { - provider: "discord", - name: "Discord", - description: "Link your Discord account to join the AeThex community.", - Icon: DiscordIcon, - gradient: "from-indigo-600 to-purple-900", - }, - ], - [], - ); - - useEffect(() => { - console.log("Dashboard useEffect:", { - user: !!user, - profile: !!profile, - authLoading, - }); - - // While auth is still resolving, keep showing loading state - if (authLoading) { - setIsLoading(true); - return; - } - - // Auth resolved - check user state - if (!user) { - console.log("No user, showing login prompt (not redirecting)"); - setIsLoading(false); - return; - } - - // User exists - load dashboard data - if (user && profile) { - console.log("User and profile exist, loading dashboard data"); - loadDashboardData(); - } else if (user && !profile) { - console.log("User exists but no profile, keeping loading screen visible"); - // Keep loading visible while waiting for profile, then onboarding will take over - // Don't set isLoading to false yet - } - }, [user, profile, authLoading]); - - // Sync local settings form with profile - useEffect(() => { - setDisplayName(profile?.full_name || ""); - setLocationInput(profile?.location || ""); - setBio(profile?.bio || ""); - setWebsite((profile as any)?.website_url || ""); - setLinkedin(profile?.linkedin_url || ""); - setGithub(profile?.github_url || ""); - setTwitter(profile?.twitter_url || ""); - setUserRealm(((profile as any)?.user_type as RealmKey) ?? null); - setExperienceLevel((profile as any)?.experience_level || "beginner"); - if (profile) computeProfileCompletion(profile); }, [profile]); - const saveProfile = async () => { + const handleSaveProfile = async () => { if (!user) return; setSavingProfile(true); try { - await updateProfile({ - full_name: displayName, - location: locationInput, - bio, - website_url: website as any, - linkedin_url: linkedin, - github_url: github, - twitter_url: twitter, - } as any); + const { error } = await (window as any).supabaseClient + .from("user_profiles") + .update({ + full_name: displayName, + bio: bio, + website_url: website, + linkedin_url: linkedin, + github_url: github, + twitter_url: twitter, + }) + .eq("id", user.id); + + if (error) throw error; + aethexToast({ + message: "Profile updated successfully!", + type: "success", + }); + } catch (error: any) { + aethexToast({ + message: error?.message || "Failed to update profile", + type: "error", + }); } finally { setSavingProfile(false); } }; - const computeProfileCompletion = (p: any) => { - const checks = [ - !!p?.full_name, - !!p?.bio, - !!p?.location, - !!p?.avatar_url, - !!p?.banner_url, - !!(p?.website_url || p?.github_url || p?.linkedin_url || p?.twitter_url), - ]; - const pct = Math.round( - (checks.filter(Boolean).length / checks.length) * 100, - ); - setProfileCompletion(pct); - }; - - const hasRealmChanges = useMemo(() => { - const currentRealm = ((profile as any)?.user_type as RealmKey) ?? null; - const currentExperience = - ((profile as any)?.experience_level as string) || "beginner"; - const selectedRealmValue = userRealm ?? null; - const selectedExperienceValue = experienceLevel || "beginner"; - return ( - selectedRealmValue !== currentRealm || - selectedExperienceValue !== currentExperience - ); - }, [profile, userRealm, experienceLevel]); - - const loadDashboardData = async () => { - try { - setIsLoading(true); - - const userId = user!.id; - - // Parallelize all independent data fetches - const [ - projectsResult, - teamsResult, - postsResult, - invitesResult, - networkResult, - applicationsResult, - achievementsResult, - followerCountResult, - ] = await Promise.allSettled([ - // Projects - aethexProjectService.getUserProjects(userId).catch(() => []), - // Teams - aethexCollabService.listMyTeams(userId).catch(() => []), - // Posts - communityService - .getUserPosts(userId) - .then((p) => p?.slice(0, 5) || []) - .catch(() => []), - // Invites - aethexSocialService - .listInvites(userId) - .then((i) => (Array.isArray(i) ? i : [])) - .catch(() => []), - // Network (following, followers, connections) - Promise.all([ - aethexSocialService.getFollowing(userId).catch(() => []), - aethexSocialService.getFollowers(userId).catch(() => []), - aethexSocialService.getConnections(userId).catch(() => []), - ]), - // Applications - supabase - .from("project_applications") - .select(`*, projects!inner(id, title, user_id)`) - .eq("projects.user_id", userId) - .order("created_at", { ascending: false }) - .limit(10) - .then(({ data }) => (Array.isArray(data) ? data : [])) - .catch(() => []), - // Achievements (don't block on checkAndAwardProjectAchievements - do it in background) - Promise.all([ - aethexAchievementService.getUserAchievements(userId).catch(() => []), - aethexAchievementService.getAllAchievements().catch(() => []), - ]).then(([earned, all]) => ({ earned: earned || [], all: all || [] })), - // Follower count - supabase - .from("user_follows") - .select("id", { count: "exact", head: true }) - .eq("following_id", userId) - .then(({ count }) => (typeof count === "number" ? count : 0)) - .catch(() => 0), - ]); - - // Extract results from settled promises - const userProjects = - projectsResult.status === "fulfilled" ? projectsResult.value : []; - setProjects(userProjects); - - const myTeams = - teamsResult.status === "fulfilled" ? teamsResult.value : []; - setTeams(myTeams); - - const userPosts = - postsResult.status === "fulfilled" ? postsResult.value : []; - setUserPosts(userPosts); - - const myInvites = - invitesResult.status === "fulfilled" ? invitesResult.value : []; - setInvites(myInvites); - - if (networkResult.status === "fulfilled") { - const [flw, fol, conns] = networkResult.value; - setFollowingIds(Array.isArray(flw) ? flw : []); - setFollowerIds(Array.isArray(fol) ? fol : []); - setConnectionsList(Array.isArray(conns) ? conns : []); - } else { - setFollowingIds([]); - setFollowerIds([]); - setConnectionsList([]); - } - - const appData = - applicationsResult.status === "fulfilled" - ? applicationsResult.value - : []; - setApplications(appData); - - let userAchievements: any[] = []; - let catalog: any[] = []; - if (achievementsResult.status === "fulfilled") { - userAchievements = achievementsResult.value.earned; - catalog = achievementsResult.value.all; - } - setAchievements(userAchievements); - setAllAchievements(catalog); - - const followerCount = - followerCountResult.status === "fulfilled" - ? followerCountResult.value - : 0; - - // Calculate stats - const activeCount = userProjects.filter( - (p) => p.status === "in_progress" || p.status === "planning", - ).length; - const completedCount = userProjects.filter( - (p) => p.status === "completed", - ).length; - - const totalXp = - typeof (profile as any)?.total_xp === "number" - ? (profile as any).total_xp - : 0; - const performanceBase = - 60 + activeCount * 5 + completedCount * 8 + userAchievements.length * 3; - const performanceScore = Math.min( - 100, - Math.round(performanceBase + totalXp / 150), - ); - - setStats({ - activeProjects: activeCount, - completedTasks: completedCount, - teamMembers: followerCount, - performanceScore: `${performanceScore}%`, - }); - - // Background task: Check and award achievements (don't block) - aethexAchievementService - .checkAndAwardProjectAchievements(userId) - .catch((e) => { - console.warn("checkAndAwardProjectAchievements failed:", e); - }); - } catch (error) { - console.error("Error loading dashboard data:", error); - aethexToast.error({ - title: "Failed to load dashboard", - description: "Please try refreshing the page", - }); - } finally { - setIsLoading(false); - } - }; - - const handleLinkProvider = async (provider: ProviderKey) => { - setConnectionAction(`${provider}-link`); - try { - await linkProvider(provider); - } finally { - setConnectionAction(null); - } - }; - - const handleUnlinkProvider = async (provider: ProviderKey) => { - setConnectionAction(`${provider}-unlink`); - try { - await unlinkProvider(provider); - } finally { - setConnectionAction(null); - } - }; - - const handleQuickAction = async (actionTitle: string) => { - switch (actionTitle) { - case "Start New Project": - navigate("/projects/new"); - break; - case "Create Team": - navigate("/teams"); - break; - case "Access Labs": - navigate("/research"); - break; - case "View Analytics": - aethexToast.info({ - title: "Analytics", - description: "Analytics dashboard coming soon!", - }); - break; - default: - aethexToast.info({ - title: actionTitle, - description: "Feature coming soon!", - }); - } - }; - - // Show loading while auth is resolving OR profile is loading - if (authLoading || isLoading || (user && !profile)) { - return ( - - - - ); + if (authLoading) { + return ; } - // If no user and auth is resolved, show login prompt (don't auto-redirect) if (!user) { return ( -
-
-

- Welcome to AeThex -

-

- You need to be signed in to access the dashboard -

- + Sign In to Dashboard + +
); } - // Hide setup banner once onboarding is complete const showProfileSetup = !profileComplete; - const statsDisplay = [ - { - label: "Active Projects", - value: stats.activeProjects, - icon: Rocket, - color: "from-blue-500 to-purple-600", - }, - { - label: "Completed Tasks", - value: stats.completedTasks, - icon: Trophy, - color: "from-green-500 to-blue-600", - }, - { - label: "Team Members", - value: stats.teamMembers, - icon: Users, - color: "from-purple-500 to-pink-600", - }, - { - label: "Performance Score", - value: stats.performanceScore, - icon: TrendingUp, - color: "from-orange-500 to-red-600", - }, - ]; - - const getProgressPercentage = (project: any) => { - switch (project.status) { - case "planning": - return 20; - case "in_progress": - return 60; - case "completed": - return 100; - default: - return 0; - } - }; - - const getPriorityFromTech = (technologies: string[]) => { - if ( - technologies.some( - (tech) => - tech.toLowerCase().includes("ai") || - tech.toLowerCase().includes("blockchain"), - ) - ) { - return "High"; - } - return "Medium"; - }; - - const getAchievementIcon = (iconName: string) => { - switch (iconName.toLowerCase()) { - case "code": - return Code; - case "users": - return Users; - case "rocket": - return Rocket; - case "database": - return Database; - case "shield": - return Shield; - case "trophy": - return Trophy; - default: - return Star; - } - }; - - const quickActions = [ - { - title: "Start New Project", - icon: Rocket, - color: "from-blue-500 to-purple-600", - }, - { title: "Create Team", icon: Users, color: "from-green-500 to-blue-600" }, - { title: "Access Labs", icon: Zap, color: "from-yellow-500 to-orange-600" }, - { - title: "View Analytics", - icon: BarChart3, - color: "from-purple-500 to-pink-600", - }, - ]; - - // Determine active realm for dashboard personalization - const activeRealm: RealmKey = (userRealm || - ((profile as any)?.user_type as RealmKey) || - "community_member") as RealmKey; - - // Show loading screen while auth is resolving or if user is not authenticated - if (authLoading || !user) { - return ; - } - return ( -
-
- {/* Profile Setup Banner */} - {showProfileSetup && ( -
- - -
-
- -
-

- Complete Your Profile -

-

- Set up your profile to unlock all features -

-
-
-
- - -
-
-
-
-
- )} - - {/* Header */} -
-
-
-
-

- {activeRealm === "game_developer" && "Game Development"} - {activeRealm === "client" && "Consulting"} - {activeRealm === "community_member" && "Community"} - {activeRealm === "customer" && "Getting Started"} - {activeRealm === "staff" && "Operations"} -

-
-

- Dashboard -

-
-
-

- Welcome back,{" "} - - {profile?.full_name || user.email?.split("@")[0]} - {" "} - • {streakLabel} +

+
+ {/* Header Section */} +
+
+
+

+ Dashboard +

+

+ Welcome back, {profile?.full_name || user.email?.split("@")[0]}

- {longestStreak > 0 && ( -
- - - {activeRealm.replace("_", " ")} Realm - - - - {profileCompletion}% Complete - -
+
+
+ {profileComplete && ( + + + Profile Complete + + )} + {profile?.level && ( + + + Level {profile.level} + )}
-
- - -
-
-
- {/* Left Sidebar - User Profile */} -
- {/* Profile Card */} - - -
-
-
- User Avatar -
-
-
-

- {profile?.full_name || user.email?.split("@")[0]} -

-

- {profile?.role || "Creator"} -

-
- - - Level {profile?.level || 1} - - {profileComplete && ( - - - Complete - - )} -
-
- - {/* XP Progress */} -
-
- Experience - - {profile?.total_xp || 0} /{" "} - {(profile?.level || 1) * 1000} - -
-
-
-
+ {/* Setup Banner */} + {showProfileSetup && ( + + +
+
+

Complete Your Profile

+

Set up your profile to unlock all AeThex features and join the community

+
+ )} +
- {/* Quick Actions */} - - - - - Quick Actions - - - - {quickActions.map((action, index) => { - const Icon = action.icon; - return ( - - ); - })} - - -
+ {/* Tabs Section */} + + + + Realms + Arms + + + Profile + + + Connections + + + Settings + + - {/* Main Content */} -
- {/* Stats Grid (adapts per realm by labels) */} -
- {statsDisplay.map((stat, index) => { - const Icon = stat.icon; + {/* Realms Tab */} + +
+ {ARMS.map((arm) => { + const IconComponent = arm.icon; return ( - { + e.preventDefault(); + navigate(arm.href); + }} + className="group relative overflow-hidden" > -
- -
-
-

- {stat.label} -

-

- {stat.value} -

+ + +
+
+
+
+ +
+

{arm.label}

+
+

{arm.description}

+
-
- +
+ Explore +
-
-
-
+ + + ); })}
+ - {/* Realm spotlight */} - {activeRealm === "game_developer" && ( - - - - Create a Post - - - Share updates, images, or videos - - - - + {/* Profile Tab */} + +
+ {/* Profile Card */} + + +
+
+ Profile +
+
+

{profile?.full_name || "User"}

+

{user.email}

+ + + Level {profile?.level || 1} + +
- )} - {activeRealm === "community_member" && ( - + {/* Edit Profile Form */} + - - Community actions - - - Post to the feed and explore trending topics - + Edit Profile + Update your public profile information - {}} /> -
- - -
-
-
- )} - - {activeRealm === "client" && ( - - - - Project workspace - - - Kick off engagements and track delivery - - - - - - - - - )} - - {activeRealm === "customer" && ( - - - Get started - - Explore products and manage access - - - - - - - - )} - - {activeRealm === "staff" && ( - - - Operations - - Moderation and internal tools - - - - - - - - )} - - {/* Settings Section */} - - - - Account Settings - - - Manage your profile, notifications, and privacy - - - - - - Profile - Connections - - Notifications - - Privacy - - - -
-
- - setDisplayName(e.target.value)} - /> -
-
- - setLocationInput(e.target.value)} - /> -
-
- - { - const ensureBuckets = async () => { - try { - await fetch( - `${API_BASE}/api/storage/ensure-buckets`, - { - method: "POST", - }, - ); - } catch {} - }; - const file = e.target.files?.[0]; - if (!file || !user) return; - const storeDataUrl = () => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => - resolve(reader.result as string); - reader.onerror = () => - reject(new Error("Failed to read file")); - reader.readAsDataURL(file); - }); - try { - await ensureBuckets(); - const path = `${user.id}/avatar-${Date.now()}-${file.name}`; - let { error } = await supabase.storage - .from("avatars") - .upload(path, file, { upsert: true }); - if ( - error && - /bucket/i.test(error?.message || "") - ) { - await ensureBuckets(); - ({ error } = await supabase.storage - .from("avatars") - .upload(path, file, { upsert: true })); - } - if (error) throw error; - const { data } = supabase.storage - .from("avatars") - .getPublicUrl(path); - await updateProfile({ - avatar_url: data.publicUrl, - } as any); - computeProfileCompletion({ - ...(profile as any), - avatar_url: data.publicUrl, - }); - aethexToast.success({ - title: "Avatar updated", - }); - } catch (err: any) { - try { - const dataUrl = await storeDataUrl(); - await updateProfile({ - avatar_url: dataUrl, - } as any); - computeProfileCompletion({ - ...(profile as any), - avatar_url: dataUrl, - }); - aethexToast.success({ - title: "Avatar saved (local)", - }); - } catch (e: any) { - aethexToast.error({ - title: "Upload failed", - description: - err?.message || "Unable to upload image", - }); - } - } - }} - /> -
-
- - { - const ensureBuckets = async () => { - try { - await fetch( - `${API_BASE}/api/storage/ensure-buckets`, - { - method: "POST", - }, - ); - } catch {} - }; - const file = e.target.files?.[0]; - if (!file || !user) return; - const storeDataUrl = () => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => - resolve(reader.result as string); - reader.onerror = () => - reject(new Error("Failed to read file")); - reader.readAsDataURL(file); - }); - try { - await ensureBuckets(); - const path = `${user.id}/banner-${Date.now()}-${file.name}`; - let { error } = await supabase.storage - .from("banners") - .upload(path, file, { upsert: true }); - if ( - error && - /bucket/i.test(error?.message || "") - ) { - await ensureBuckets(); - ({ error } = await supabase.storage - .from("banners") - .upload(path, file, { upsert: true })); - } - if (error) throw error; - const { data } = supabase.storage - .from("banners") - .getPublicUrl(path); - await updateProfile({ - banner_url: data.publicUrl, - } as any); - computeProfileCompletion({ - ...(profile as any), - banner_url: data.publicUrl, - }); - aethexToast.success({ - title: "Banner updated", - }); - } catch (err: any) { - try { - const dataUrl = await storeDataUrl(); - await updateProfile({ - banner_url: dataUrl, - } as any); - computeProfileCompletion({ - ...(profile as any), - banner_url: dataUrl, - }); - aethexToast.success({ - title: "Banner saved (local)", - }); - } catch (e: any) { - aethexToast.error({ - title: "Upload failed", - description: - err?.message || "Unable to upload image", - }); - } - } - }} - /> -
-
- -