"use client"; import { useEffect, useMemo, useRef, useState } from "react"; interface DailyActivity { date: string; flashcards: number; questions: number; total: number; level: 0 | 1 | 2 | 3 | 4; } interface ActivitySummary { days: DailyActivity[]; today: string; currentStreak: number; } const levelClasses = [ "bg-white/10", "bg-primary/45", "bg-primary", "bg-accent", "bg-[#fbbf24]", ] as const; export function ActivityBanner({ onNewClass }: { onNewClass: () => void }) { const [summary, setSummary] = useState(null); const [error, setError] = useState(false); useEffect(() => { fetch("/api/activity") .then((response) => { if (!response.ok) throw new Error("Failed to load activity"); return response.json(); }) .then(setSummary) .catch(() => setError(true)); }, []); return (

Personal library

Study activity

{error && (
Study activity could not be loaded. Your library is still available below.
)} {!summary && !error &&
} {summary && (
)}
); } function ActivityHeatmap({ days, today }: { days: DailyActivity[]; today: string }) { const scrollRef = useRef(null); const [activeDay, setActiveDay] = useState(null); const weeks = useMemo( () => Array.from({ length: 53 }, (_, index) => days.slice(index * 7, index * 7 + 7)), [days] ); useEffect(() => { const container = scrollRef.current; if (container) container.scrollLeft = container.scrollWidth; }, [days]); return (

Last 53 weeks · Arizona time

{activeDay && }
Mon
Wed
Fri
{weeks.map((week, index) => (
{getMonthLabel(week, weeks[index - 1])}
))}
{weeks.map((week, weekIndex) => (
{week.map((day) => day.date > today ? ( ) : (
))}
Less {levelClasses.map((className, index) => )} More
); } function DayTooltip({ day }: { day: DailyActivity }) { const formatted = new Date(`${day.date}T12:00:00Z`).toLocaleDateString("en-US", { timeZone: "America/Phoenix", month: "short", day: "numeric", year: "numeric", }); return (
{formatted} · {day.total} total
{day.flashcards} flashcards · {day.questions} questions
); } function StreakDisplay({ streak }: { streak: number }) { const size = 24 + Math.min(streak, 30) * 0.6; const color = streak === 0 ? "text-white/30" : streak < 7 ? "text-accent" : streak < 30 ? "text-[#ff7a36]" : "text-[#fbbf24]"; return (
{streak}
day streak
20 activities per day
); } function getMonthLabel(week: DailyActivity[], previousWeek?: DailyActivity[]) { const firstOfMonth = week.find((day) => Number(day.date.slice(8, 10)) <= 7); if (!firstOfMonth) return ""; const month = firstOfMonth.date.slice(5, 7); if (previousWeek?.some((day) => day.date.slice(5, 7) === month)) return ""; return new Date(`${firstOfMonth.date}T12:00:00Z`).toLocaleDateString("en-US", { month: "short", timeZone: "America/Phoenix" }); } function getDayAriaLabel(day: DailyActivity) { return `${day.date}: ${day.flashcards} flashcards and ${day.questions} questions, ${day.total} total activities`; }