"use client" import { useAuth } from "@/context/auth-context" import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import { toast } from "react-hot-toast" import { User, LogOut, Settings, Trophy, BookOpen, Target, TrendingUp, Wallet, Mail, Calendar, Award, BarChart3, Activity, Edit3, Save, X, Loader2, Github, Linkedin, Twitter, Link2, Flame, Upload } from "lucide-react" import api from "@/lib/api" type ActivityData = { id: string type: string title: string description: string completed_at: string timestamp_utc?: string points_earned?: number } export default function DashboardPage() { const { user, walletConnected, logout, authMethod } = useAuth() const router = useRouter() const normalizedRole = String(user?.role || 'student').toLowerCase() const roleLabel = normalizedRole === 'admin' ? 'Admin' : normalizedRole === 'instructor' ? 'Instructor' : 'Student' const roleBadgeClass = normalizedRole === 'admin' ? 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-200' : normalizedRole === 'instructor' ? 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200' : 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200' const [isEditingProfile, setIsEditingProfile] = useState(false) const [isLoadingStats, setIsLoadingStats] = useState(true) const [isEditingSocial, setIsEditingSocial] = useState(false) const [isUploadingImage, setIsUploadingImage] = useState(false) const [showAllActivities, setShowAllActivities] = useState(false) const [recentActivity, setRecentActivity] = useState([]) const [profileData, setProfileData] = useState({ name: user?.name || '', bio: user?.bio || '', avatar: user?.avatar || '' }) const [socialData, setSocialData] = useState({ github: '', linkedin: '', twitter: '' }) const [stats, setStats] = useState({ coursesCompleted: 0, totalXP: 0, currentStreak: 0, bestStreak: 0, rank: 0, certificatesEarned: 0, hoursLearned: 0, lastActiveDate: new Date().toISOString() }) // Fetch real stats from API useEffect(() => { if (!user) { router.replace("/auth/login") return } fetchRealStats() }, [user, router]) const fetchRealStats = async () => { setIsLoadingStats(true) try { const [statsResponse, activityResponse] = await Promise.all([ api.get("/api/dashboard/comprehensive-stats"), api.get("/api/dashboard/recent-activity"), ]) if (statsResponse.data.success && statsResponse.data.data) { const data = statsResponse.data.data const streakData = data.streak_data || {} setStats({ coursesCompleted: data.courses_completed || 0, totalXP: data.total_xp || 0, currentStreak: streakData.current_streak || 0, bestStreak: streakData.best_streak || 0, rank: data.global_rank || 0, certificatesEarned: data.blockchain?.certificates || 0, hoursLearned: Math.round(data.learning_analytics?.time_spent_hours || 0), lastActiveDate: data.last_active_date || new Date().toISOString() }) } if (activityResponse.data?.success && Array.isArray(activityResponse.data?.data)) { setRecentActivity(activityResponse.data.data) } } catch (error: any) { console.error("Failed to fetch dashboard stats:", error) // Keep default values if fetch fails toast.error("Failed to load dashboard data") } finally { setIsLoadingStats(false) } } const handleSettingsClick = () => { setIsEditingSocial(false) setIsEditingProfile(true) const el = document.getElementById("profile-card") if (el) { el.scrollIntoView({ behavior: "smooth", block: "start" }) } } const activityIconConfig = (activityType: string) => { const t = String(activityType || "").toLowerCase() if (t.includes("course")) return { icon: BookOpen, bgColor: "bg-green-100", textColor: "text-green-600" } if (t.includes("quiz")) return { icon: Award, bgColor: "bg-blue-100", textColor: "text-blue-600" } if (t.includes("streak")) return { icon: Flame, bgColor: "bg-orange-100", textColor: "text-orange-600" } if (t.includes("rank")) return { icon: TrendingUp, bgColor: "bg-purple-100", textColor: "text-purple-600" } if (t.includes("account") || t.includes("auth")) return { icon: Settings, bgColor: "bg-indigo-100", textColor: "text-indigo-600" } return { icon: Activity, bgColor: "bg-slate-100", textColor: "text-slate-600" } } const isPlaceholderActivity = (item: ActivityData) => { const text = `${item.title || ""} ${item.description || ""}`.toLowerCase() const fakeMarkers = [ "completed react fundamentals", "scored 95% on javascript quiz", "7-day learning streak achieved", "moved up 5 positions in leaderboard", ] return fakeMarkers.some((marker) => text.includes(marker)) } const realActivities = recentActivity.filter((item) => !isPlaceholderActivity(item)) const visibleActivities = showAllActivities ? realActivities : realActivities.slice(0, 6) const handleProfileUpdate = async () => { try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") if (!token) { toast.error("Not authenticated") return } const response = await api.post( "/api/auth/profile/update", { name: profileData.name, bio: profileData.bio, avatar: profileData.avatar }, { headers: { "Authorization": `Bearer ${token}` } } ) if (response.data.success) { setIsEditingProfile(false) toast.success("Profile updated successfully") // Update local user context if available console.log("Profile updated:", response.data.user) } else { toast.error(response.data.error || "Failed to update profile") } } catch (error: any) { console.error("Failed to update profile:", error) toast.error(error.response?.data?.error || "Failed to update profile") } } const handleImageUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return // Validate file type const allowedTypes = ['image/png', 'image/jpeg'] if (!allowedTypes.includes(file.type)) { toast.error('Only PNG and JPG formats are allowed') return } // Validate file size (5MB max) if (file.size > 5 * 1024 * 1024) { toast.error('File size must be less than 5MB') return } setIsUploadingImage(true) try { const token = localStorage.getItem("openlearnx_jwt_token") || localStorage.getItem("openlearnx_token") if (!token) { toast.error("Not authenticated") return } const formData = new FormData() formData.append('file', file) const response = await api.post( "/api/auth/upload-image", formData, { headers: { "Authorization": `Bearer ${token}`, "Content-Type": "multipart/form-data" } } ) if (response.data.success) { setProfileData({ ...profileData, avatar: response.data.image }) toast.success("Image uploaded successfully") } else { toast.error(response.data.error || "Failed to upload image") } } catch (error: any) { console.error("Image upload error:", error) toast.error(error.response?.data?.error || "Failed to upload image") } finally { setIsUploadingImage(false) } } const handleSocialUpdate = async () => { try { // Here you would call your API to update social links // await updateSocialLinks(socialData) console.log("Social links updated:", socialData) toast.success("Social links updated successfully") } catch (error) { console.error("Failed to update social links:", error) toast.error("Failed to update social links") } } if (!user) { return (
) } return (
{/* Professional Header */}

OpenLearnX

Learn • Earn • Grow

{/* Main Dashboard Content */}
{/* Welcome Section */}

Welcome back

Ready to continue your learning journey?

{authMethod === "metamask" && user ? (
Connected: {user.wallet_address.slice(0, 6)}...{user.wallet_address.slice(-4)} {roleLabel}
) : (
{user.email || user.id} {roleLabel}
)}
{/* Stats Grid */}
{isLoadingStats ? ( // Loading skeleton <> {[1, 2, 3, 4].map((i) => (
))} ) : ( <>

Total XP

{stats.totalXP.toLocaleString()}

+12% from last week

Courses

{stats.coursesCompleted}

3 in progress

Streak

{stats.currentStreak} days

Keep your streak going

Global Rank

#{stats.rank}

Top 5% learner
)}
{/* Main Content Grid */}
{/* Profile Card with Edit Functionality */}
{/* Profile Tabs */}
{!isEditingSocial ? ( /* Profile Tab */ <>
{profileData.avatar ? ( Avatar ) : (
)} {isEditingProfile ? (
setProfileData({...profileData, name: e.target.value})} placeholder="Your name" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-center" /> {/* Image Upload Input */}

Max 5MB (PNG or JPG only)