From 91e373624f61bf93010383647050393da9bb9a87 Mon Sep 17 00:00:00 2001 From: sruju333 Date: Thu, 24 Sep 2026 20:31:31 -0400 Subject: [PATCH] week2: Wire StreakModal to live streak data; record completedDates on full daily completion --- middlewareNode/src/routes/activities.js | 31 ++++++--- .../student-profile/Modals/StreakModal.scss | 28 +++++++- .../student-profile/Modals/StreakModal.tsx | 68 +++++++++++++++---- .../student-profile/NewStudentProfile.tsx | 2 +- 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/middlewareNode/src/routes/activities.js b/middlewareNode/src/routes/activities.js index f9b68c7c..2a6b4002 100644 --- a/middlewareNode/src/routes/activities.js +++ b/middlewareNode/src/routes/activities.js @@ -140,17 +140,32 @@ router.put("/:username/activity", requireAuth, requireActivityWriteAccess, async return res.status(404).json({error:'User not found'}); } const activities = db.collection("activities"); - const activityIncomplete = await activities.findOne( - { userId, "activities.name": activityName }, - { activities: {$elemMatch: { name: activityName }}, _id:0}, - ); - if(activityIncomplete) { - console.log('incomplete activity: ', activityName); - } + await activities.updateOne( { userId, "activities.name": activityName }, { $set: { "activities.$.completed": true } } ); + + // If every daily activity is now complete, record today's date + // (once) in completedDates — used for streak/badge stats. + const doc = await activities.findOne({ userId }); + const allCompleted = doc?.activities?.length > 0 && + doc.activities.every((a) => a.completed === true); + + if (allCompleted) { + const today = new Date(); + today.setUTCHours(0, 0, 0, 0); + const alreadyRecorded = (doc.completedDates || []).some( + (d) => new Date(d).getTime() === today.getTime() + ); + if (!alreadyRecorded) { + await activities.updateOne( + { userId }, + { $push: { completedDates: today } } + ); + } + } + return res.status(200).json({message:'success'}); } catch (err) { console.error('Error updating activities: ', err); @@ -158,4 +173,4 @@ router.put("/:username/activity", requireAuth, requireActivityWriteAccess, async } }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss index 6c085fe7..7e4caccc 100644 --- a/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss +++ b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.scss @@ -188,19 +188,41 @@ } // ========================== -// Calendar Image Section +// Calendar Grid Section // ========================== .calendar-image-wrapper { display: flex; justify-content: center; margin-top: 2rem; - .calendar-image { + .calendar-grid { width: 55%; max-width: 37.5rem; - height: auto; + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 0.5rem; + background-color: #fff; border-radius: 0.75rem; box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.15); + padding: 1rem; + + .calendar-day { + aspect-ratio: 1 / 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-family: 'Lato', sans-serif; + font-weight: 600; + font-size: 0.9rem; + color: #666; + background-color: #F0F0F0; + + &.completed { + background-color: #A8D96C; + color: #fff; + } + } } } diff --git a/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx index 3e520489..5d7fa8ef 100644 --- a/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx +++ b/react-ystemandchess/src/features/student/student-profile/Modals/StreakModal.tsx @@ -3,31 +3,37 @@ * * Displays a modal showing the user's daily activity streak progress. * Features animated characters (Stemette and Stemmy) with encouraging messages, - * a visual streak tracker with clock icon, and a weekly calendar view. + * a visual streak tracker with clock icon, and a real calendar view fetched + * from the streak API. * * Features: * - Animated character mascots with speech bubbles - * - Visual streak progress indicator - * - Weekly calendar tracking (placeholder for now) + * - Visual streak progress indicator, fetched from GET /streak + * - Monthly calendar tracking completed days, fetched from GET /streak/calendar * - Click outside to close functionality */ -import React from "react"; +import React, { useEffect, useState } from "react"; +import { useCookies } from "react-cookie"; import "./StreakModal.scss"; import { ReactComponent as Polygon } from "../../../../assets/images/StreakProgressAssets/polygon.svg"; import { ReactComponent as Polygon_2 } from "../../../../assets/images/StreakProgressAssets/polygon_2.svg"; import streakClock from "../../../../assets/images/StreakProgressAssets/streak_progress_clock.png"; import { ReactComponent as Stemette } from "../../../../assets/images/StreakProgressAssets/stemette.svg"; import { ReactComponent as Stemmy } from "../../../../assets/images/StreakProgressAssets/stemmy.svg"; - -// Calendar placeholder. Delete once an actual calendar is implemented -import calendarIcon from "../../../../assets/images/StreakProgressAssets/Calendar.png"; +import { environment } from "../../../../environments/environment"; /** * StreakModal component - displays user's streak progress * @param {Function} onClose - Callback to close the modal + * @param {string} username - Username to fetch streak data for */ -const StreakModal = ({ onClose }: { onClose: () => void }) => { +const StreakModal = ({ onClose, username }: { onClose: () => void; username: string }) => { + const [cookies] = useCookies(["login"]); + const [currentStreak, setCurrentStreak] = useState(0); + const [calendarDays, setCalendarDays] = useState<{ date: string; completed: boolean }[]>([]); + const [loading, setLoading] = useState(true); + // Overlay click handler - closes modal only if clicking outside modal content const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { @@ -35,6 +41,32 @@ const StreakModal = ({ onClose }: { onClose: () => void }) => { } }; + useEffect(() => { + const fetchStreakData = async () => { + try { + const streakRes = await fetch( + `${environment.urls.middlewareURL}/streak?username=${username}`, + { headers: { Authorization: `Bearer ${cookies.login}` } } + ); + const streakData = await streakRes.json(); + setCurrentStreak(streakData.currentStreak ?? 0); + + const month = new Date().toISOString().slice(0, 7); // "YYYY-MM" + const calRes = await fetch( + `${environment.urls.middlewareURL}/streak/calendar?username=${username}&month=${month}`, + { headers: { Authorization: `Bearer ${cookies.login}` } } + ); + const calData = await calRes.json(); + setCalendarDays(calData.days || []); + } catch (err) { + console.error("Failed to fetch streak data:", err); + } finally { + setLoading(false); + } + }; + fetchStreakData(); + }, [username, cookies.login]); + return (
@@ -66,19 +98,31 @@ const StreakModal = ({ onClose }: { onClose: () => void }) => { Streak Clock
-

9

+

{loading ? "…" : currentStreak}

Day Streak

Today is

-

6/9

+

+ {new Date().toLocaleDateString("en-US", { month: "numeric", day: "numeric" })} +

- {/* Calendar placeholder. Delete once an actual calendar is implemented */} + {/* Monthly calendar, fetched from GET /streak/calendar */}
- Calendar Icon +
+ {calendarDays.map((day) => ( +
+ {new Date(day.date).getDate()} +
+ ))} +
diff --git a/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx index de971eba..bc496fb8 100644 --- a/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx +++ b/react-ystemandchess/src/features/student/student-profile/NewStudentProfile.tsx @@ -689,7 +689,7 @@ const NewStudentProfile = ({ userPortraitSrc }: any) => { {/* Modals */} - {activeModal === "streak" && setActiveModal(null)} />} + {activeModal === "streak" && setActiveModal(null)} username={username} />} {activeModal === "activities" && setActiveModal(null)} username={username} />} {activeModal === "badges" && setActiveModal(null)} username={username} />} {activeModal === "leaderboard" && setActiveModal(null)} />}