diff --git a/app/components/AuthModal.jsx b/app/components/AuthModal.jsx deleted file mode 100755 index a2a446e..0000000 --- a/app/components/AuthModal.jsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; -import LoginForm from './loginForm'; -import SignupForm from './signupForm'; - -export default function AuthModal({ isOpen, showLogin, closeModal, switchToLogin, switchToSignup }) { - if (!isOpen) return null; - - return ( -
-
- - {showLogin ? ( - - ) : ( - - )} -
-
- ); -} \ No newline at end of file diff --git a/app/components/dashboard/ActivityDashboard.jsx b/app/components/dashboard/ActivityDashboard.jsx new file mode 100755 index 0000000..cd266ff --- /dev/null +++ b/app/components/dashboard/ActivityDashboard.jsx @@ -0,0 +1,55 @@ +import React, { useEffect, useState } from "react"; +import { supabase } from "@/lib/supabase"; +import StreakCounter from "@/app/components/dashboard/StreakCounter"; +import ActivityHeatmap from "@/app/components/dashboard/ActivityHeatmap"; +import {ChartNoAxesCombined} from "lucide-react"; + +function ActivityDashboard({ userId }) { + const [activityDates, setActivityDates] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!userId) return; + + async function fetchActivity() { + setLoading(true); + const { data, error } = await supabase + .from("user_activity") + .select("activity_date") + .eq("user_id", userId); + + if (!error && data) { + const dates = data.map( + (item) => new Date(item.activity_date).toISOString().split("T")[0] + ); + setActivityDates(dates); + } + setLoading(false); + } + + fetchActivity(); + }, [userId]); + + if (loading) { + return ( +
+ Loading activity... +
+ ); + } + + return ( +
+
+ +

Your Stats

+
+
+ + +
+
+ ); +} + +export default ActivityDashboard; diff --git a/app/components/dashboard/ActivityHeatmap.jsx b/app/components/dashboard/ActivityHeatmap.jsx new file mode 100755 index 0000000..346e767 --- /dev/null +++ b/app/components/dashboard/ActivityHeatmap.jsx @@ -0,0 +1,132 @@ +import React, { useMemo } from "react"; + +function ActivityHeatmap({ activityDates }) { + // Generate last 90 days + const last90Days = useMemo(() => { + const dates = []; + const today = new Date(); + for (let i = 89; i >= 0; i--) { + const d = new Date(); + d.setDate(today.getDate() - i); + dates.push(d); + } + return dates; + }, []); + + // Map activity dates to a Set for O(1) lookup + const activitySet = useMemo(() => new Set(activityDates), [activityDates]); + + // Helper to format date YYYY-MM-DD + const formatDate = (date) => date.toISOString().split("T")[0]; + + // Weekday labels to show on the left (Sun, Mon, Wed, Fri) + const weekdayLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const visibleWeekdayLabels = ["Sun", "Mon", "Wed", "Fri"]; + + // Prepare weeks data (group dates by week starting on Sunday) + // We want columns = weeks, rows = days (0=Sun to 6=Sat) + const weeks = []; + let currentWeek = []; + let currentWeekStartDay = last90Days[0].getDay(); + // Pad first week with nulls if first day is not Sunday + for (let i = 0; i < currentWeekStartDay; i++) { + currentWeek.push(null); + } + last90Days.forEach((date) => { + if (currentWeek.length === 7) { + weeks.push(currentWeek); + currentWeek = []; + } + currentWeek.push(date); + }); + // Fill last week with nulls if needed + while (currentWeek.length < 7) { + currentWeek.push(null); + } + weeks.push(currentWeek); + + // Get month labels for top row + // For each week, show the month label only once when the month changes + const monthLabels = []; + let lastMonth = null; + weeks.forEach((week) => { + // Find first non-null date in week + const firstDate = week.find((d) => d !== null); + if (firstDate) { + const month = firstDate.toLocaleString("default", { month: "short" }); + if (month !== lastMonth) { + monthLabels.push(month); + lastMonth = month; + } else { + monthLabels.push(""); + } + } else { + monthLabels.push(""); + } + }); + + const totalContributions = activityDates.length; + + return ( +
+
+ {/* Left column with month label row height and weekday labels */} +
+ {/* Contributions circle at top-left corner */} +
+
+ {totalContributions} +
+
+ {/* Weekday labels column */} +
+ {weekdayLabels.map((day, idx) => + visibleWeekdayLabels.includes(day) ? ( +
+ {day} +
+ ) : ( +
+ ) + )} +
+
+
+ {/* Month labels row */} +
+ {monthLabels.map((month, idx) => ( +
+ {month} +
+ ))} +
+ {/* Heatmap grid */} +
+ {weeks.map((week, weekIdx) => + week.map((date, dayIdx) => { + if (!date) { + return
; + } + const dateStr = formatDate(date); + const isActive = activitySet.has(dateStr); + return ( +
+ ); + }) + )} +
+
+
+
+ ); +} + +export default ActivityHeatmap; diff --git a/app/components/dashboard/StreakCounter.jsx b/app/components/dashboard/StreakCounter.jsx new file mode 100755 index 0000000..5ac444c --- /dev/null +++ b/app/components/dashboard/StreakCounter.jsx @@ -0,0 +1,57 @@ +import React, { useMemo } from "react"; +import { Award } from "lucide-react"; + +function StreakCounter({ activityDates }) { + const { currentStreak, highestStreak } = useMemo(() => { + if (!activityDates || activityDates.length === 0) { + return { currentStreak: 0, highestStreak: 0 }; + } + + const dates = activityDates.map(d => new Date(d)).sort((a, b) => a - b); + + let highest = 0; + let tempStreak = 1; + + for (let i = 1; i < dates.length; i++) { + const diff = (dates[i] - dates[i - 1]) / (1000 * 60 * 60 * 24); + if (diff === 1) tempStreak++; + else tempStreak = 1; + if (tempStreak > highest) highest = tempStreak; + } + + let streakCount = 0; + const today = new Date(); + for (let i = dates.length - 1; i >= 0; i--) { + const diff = (today - dates[i]) / (1000 * 60 * 60 * 24); + if (diff === 0 || diff === 1) { + streakCount++; + today.setDate(today.getDate() - 1); + } else break; + } + + return { currentStreak: streakCount, highestStreak: highest }; + }, [activityDates]); + + return ( +
+
+ {/* Circle with fire icon and current streak */} +
+ + fire + + + {currentStreak} + +
+ + {/* Highest streak label */} +
+ Highest: {highestStreak} day{highestStreak !== 1 ? "s" : ""} +
+
+
+ ); +} + +export default StreakCounter; \ No newline at end of file diff --git a/app/components/navbar.jsx b/app/components/navbar.jsx index 1d53365..746d369 100755 --- a/app/components/navbar.jsx +++ b/app/components/navbar.jsx @@ -193,7 +193,7 @@ const AboutItem = ({ title, description, icon, iconBg, href }) => ( ); -// Abut dropdown component for desktop +// About dropdown component for desktop const AboutServicesDropdown = () => (
@@ -315,7 +315,7 @@ const handleLogout = async () => { onClick={() => setIsUserMenuOpen(!isUserMenuOpen)} /> {isUserMenuOpen && ( -
+
{ if (!user) { @@ -53,67 +56,92 @@ export default function Dashboard() { } return ( -
+
{user && ( -
- +
+
+ Welcome, {user.user_metadata?.name || user.email.split("@")[0]} +
+
+ )} + + {user && ( +
+
)}
-

Modules Completed

- {modules.filter((mod) => progress[mod.id]?.is_done).length > 0 ? ( -
- {modules - .filter((mod) => progress[mod.id]?.is_done) - .map((mod) => ( -
-
- {mod.title} -

{mod.title}

-

{mod.description}

-
-
- Completed on: {new Date(progress[mod.id].updated_at).toLocaleDateString()} -
+

Modules Completed

+ {(() => { + const completedModules = modules.filter((mod) => progress[mod.id]?.is_done); + if (completedModules.length > 0) { + const modulesToShow = showAllCompleted ? completedModules : completedModules.slice(0, 3); + return ( + <> +
+ {modulesToShow.map((mod) => ( +
+
+ {mod.title} +

{mod.title}

+

{mod.description}

+
+
+
+

Conquered : {new Date(progress[mod.id].updated_at).toLocaleDateString()}

+
+
+
+ ))}
- ))} -
- ) : ( -
-

- You haven't completed any modules yet. -

- - Start Learning - -
- )} + {completedModules.length > 3 && ( +
+ +
+ )} + + ); + } else { + return ( +
+

+ You haven't completed any modules yet. +

+ + Start Learning + +
+ ); + } + })()}
- - - ← Back to Home -
-
+ +
+
+
+ ); } \ No newline at end of file diff --git a/app/login/page.jsx b/app/login/page.jsx index c0a7978..dc5e4df 100755 --- a/app/login/page.jsx +++ b/app/login/page.jsx @@ -82,14 +82,14 @@ export default function LoginPage() { } return ( -
+
{/* Header */} -
+

{isLogin ? 'Welcome Back' : 'Create Account'}

@@ -116,7 +116,7 @@ export default function LoginPage() {

setEmail(e.target.value)} @@ -129,7 +129,7 @@ export default function LoginPage() {
setPassword(e.target.value)} @@ -143,8 +143,8 @@ export default function LoginPage() {
)} @@ -208,7 +208,7 @@ export default function LoginPage() {