aethex-forge/client/pages/Dashboard.tsx
Builder.io cbd6c20cb5 Fix Dashboard user property access to use profile data
cgen-aa291d4191594c71af67d2bd15de1760
2025-08-16 04:13:07 +00:00

537 lines
18 KiB
TypeScript

import { useState, useEffect } from "react";
import { useNavigate } 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 {
aethexProjectService,
aethexAchievementService,
} from "@/lib/aethex-database-adapter";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import LoadingScreen from "@/components/LoadingScreen";
import { Link } from "react-router-dom";
import {
User,
Settings,
Activity,
TrendingUp,
Zap,
Target,
Users,
Calendar,
Bell,
Star,
Trophy,
Rocket,
Code,
Database,
Shield,
ChevronRight,
MoreHorizontal,
Play,
Pause,
BarChart3,
} from "lucide-react";
export default function Dashboard() {
const navigate = useNavigate();
const { user, profile, loading: authLoading } = useAuth();
const [isLoading, setIsLoading] = useState(true);
const [projects, setProjects] = useState([]);
const [achievements, setAchievements] = useState([]);
const [stats, setStats] = useState({
activeProjects: 0,
completedTasks: 0,
teamMembers: 0,
performanceScore: "0%",
});
useEffect(() => {
// Force redirect immediately if no user
if (!user) {
setIsLoading(false);
// Use both React Router and window location as fallback
navigate("/login", { replace: true });
// Fallback in case React Router fails
setTimeout(() => {
if (!user) {
window.location.href = "/login";
}
}, 500);
return;
}
if (user && profile) {
loadDashboardData();
} else if (user && !profile && !authLoading) {
setIsLoading(false);
}
}, [user, profile, authLoading, navigate]);
const loadDashboardData = async () => {
try {
setIsLoading(true);
// Load user's projects
const userProjects = await aethexProjectService.getUserProjects(user!.id);
setProjects(userProjects);
// Load user's achievements
const userAchievements =
await aethexAchievementService.getUserAchievements(user!.id);
setAchievements(userAchievements);
// Calculate stats
const activeCount = userProjects.filter(
(p) => p.status === "in_progress",
).length;
const completedCount = userProjects.filter(
(p) => p.status === "completed",
).length;
setStats({
activeProjects: activeCount,
completedTasks: completedCount,
teamMembers: 8, // Mock for now
performanceScore: `${Math.min(95, 70 + completedCount * 5)}%`,
});
} 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 handleQuickAction = async (actionTitle: string) => {
switch (actionTitle) {
case "Start New Project":
navigate("/projects/new");
break;
case "Join 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!",
});
}
};
// Don't show loading screen if no user - just redirect
if (!user) {
return null;
}
// Show loading only if we have a user but are still loading data
if (authLoading || isLoading) {
return (
<LoadingScreen
message="Loading your dashboard..."
showProgress={true}
duration={1500}
/>
);
}
// Don't render if no profile
if (!profile) {
return null;
}
const statsDisplay = [
{
label: "Active Projects",
value: stats.activeProjects,
icon: Rocket,
color: "from-blue-500 to-purple-600",
},
{
label: "Completed Tasks",
value: 47,
icon: Trophy,
color: "from-green-500 to-blue-600",
},
{
label: "Team Members",
value: 8,
icon: Users,
color: "from-purple-500 to-pink-600",
},
{
label: "Performance Score",
value: "94%",
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: "Join 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",
},
];
if (isLoading) {
return (
<LoadingScreen
message="Loading your dashboard..."
showProgress={true}
duration={1000}
/>
);
}
return (
<Layout>
<div className="min-h-screen bg-aethex-gradient py-8">
<div className="container mx-auto px-4 max-w-7xl">
{/* Header */}
<div className="mb-8 animate-slide-down">
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
<div>
<h1 className="text-3xl font-bold text-gradient-purple">
Welcome back, {profile?.full_name || user.email?.split('@')[0]}
</h1>
<p className="text-muted-foreground">
{profile?.role || 'Member'} Level {profile?.level || 1} 7 day streak 🔥
</p>
</div>
<div className="flex items-center space-x-4">
<Button variant="outline" size="sm" className="hover-lift">
<Bell className="h-4 w-4 mr-2" />
Notifications
</Button>
<Button variant="outline" size="sm" className="hover-lift">
<Settings className="h-4 w-4 mr-2" />
Settings
</Button>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Left Sidebar - User Profile */}
<div className="lg:col-span-3 space-y-6">
{/* Profile Card */}
<Card className="bg-card/50 border-border/50 hover:border-aethex-400/50 transition-all duration-300 animate-scale-in">
<CardContent className="p-6">
<div className="text-center space-y-4">
<div className="relative">
<img
src={user.avatar}
alt="User Avatar"
className="w-20 h-20 rounded-full mx-auto ring-4 ring-aethex-400/20 hover:ring-aethex-400/50 transition-all duration-300"
/>
<div className="absolute -bottom-1 -right-1 w-6 h-6 bg-green-500 rounded-full border-2 border-background animate-pulse" />
</div>
<div>
<h3 className="font-semibold text-gradient">
{user.name}
</h3>
<p className="text-sm text-muted-foreground">
{user.role}
</p>
<Badge
variant="outline"
className="mt-2 border-aethex-400/50 text-aethex-400"
>
Level {user.level}
</Badge>
</div>
{/* XP Progress */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>XP Progress</span>
<span>
{user.xp} / {user.nextLevelXp}
</span>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-gradient-to-r from-aethex-500 to-neon-blue h-2 rounded-full transition-all duration-500 glow-blue"
style={{
width: `${(user.xp / user.nextLevelXp) * 100}%`,
}}
/>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Quick Actions */}
<Card className="bg-card/50 border-border/50 animate-slide-left">
<CardHeader>
<CardTitle className="text-lg">Quick Actions</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{quickActions.map((action, index) => {
const Icon = action.icon;
return (
<Button
key={index}
variant="outline"
className="w-full justify-start hover-lift interactive-scale"
style={{ animationDelay: `${index * 0.1}s` }}
>
<div
className={`p-1 rounded bg-gradient-to-r ${action.color} mr-3`}
>
<Icon className="h-4 w-4 text-white" />
</div>
{action.title}
</Button>
);
})}
</CardContent>
</Card>
</div>
{/* Main Content */}
<div className="lg:col-span-9 space-y-6">
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 animate-slide-up">
{statsDisplay.map((stat, index) => {
const Icon = stat.icon;
return (
<Card
key={index}
className="bg-card/50 border-border/50 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-scale-in"
style={{ animationDelay: `${index * 0.1}s` }}
>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">
{stat.label}
</p>
<p className="text-2xl font-bold text-gradient">
{stat.value}
</p>
</div>
<div
className={`p-3 rounded-lg bg-gradient-to-r ${stat.color}`}
>
<Icon className="h-6 w-6 text-white" />
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{/* Recent Projects */}
<Card className="bg-card/50 border-border/50 animate-fade-in">
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle className="text-gradient">
Recent Projects
</CardTitle>
<CardDescription>
Your active development projects
</CardDescription>
</div>
<Button variant="outline" size="sm" className="hover-lift">
View All
<ChevronRight className="h-4 w-4 ml-1" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{projects.slice(0, 3).map((project: any, index) => (
<div
key={index}
className="flex items-center justify-between p-4 rounded-lg border border-border/30 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-slide-right"
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center space-x-4">
<div className="w-12 h-12 rounded-lg bg-gradient-to-r from-aethex-500/20 to-neon-blue/20 flex items-center justify-center">
<Rocket className="h-6 w-6 text-aethex-400" />
</div>
<div>
<h4 className="font-semibold">{project.title}</h4>
<p className="text-sm text-muted-foreground">
{project.status?.replace("_", " ").toUpperCase()} {" "}
{project.technologies?.slice(0, 2).join(", ") ||
"No tech specified"}
</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-right">
<p className="text-sm font-medium">
{getProgressPercentage(project)}%
</p>
<div className="w-20 bg-muted rounded-full h-2 mt-1">
<div
className="bg-gradient-to-r from-aethex-500 to-neon-blue h-2 rounded-full"
style={{
width: `${getProgressPercentage(project)}%`,
}}
/>
</div>
</div>
<Badge
variant={
getPriorityFromTech(project.technologies || []) ===
"High"
? "destructive"
: "secondary"
}
className="animate-pulse"
>
{getPriorityFromTech(project.technologies || [])}
</Badge>
<Button
variant="ghost"
size="sm"
className="hover-lift"
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
</div>
))}
</CardContent>
</Card>
{/* Achievements */}
<Card className="bg-card/50 border-border/50 animate-slide-up">
<CardHeader>
<CardTitle className="text-gradient">Achievements</CardTitle>
<CardDescription>
Your progress and accomplishments
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{achievements.map((achievement, index) => {
const Icon = achievement.icon;
return (
<div
key={index}
className={`p-4 rounded-lg border transition-all duration-300 hover-lift animate-scale-in ${
achievement.earned
? "border-aethex-400/50 bg-aethex-500/10"
: "border-border/30 opacity-60"
}`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<div className="flex items-center space-x-3">
<div
className={`p-2 rounded-lg ${
achievement.earned
? "bg-gradient-to-r from-aethex-500 to-neon-blue"
: "bg-muted"
}`}
>
<Icon
className={`h-5 w-5 ${achievement.earned ? "text-white" : "text-muted-foreground"}`}
/>
</div>
<div>
<h4
className={`font-semibold ${achievement.earned ? "text-gradient" : ""}`}
>
{achievement.title}
</h4>
<p className="text-sm text-muted-foreground">
{achievement.description}
</p>
</div>
{achievement.earned && (
<Star className="h-5 w-5 text-yellow-500 animate-pulse" />
)}
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
</Layout>
);
}