Add study activity tracking and refresh class header UI
All checks were successful
Automated Container Build / build-and-push (push) Successful in 54s

This commit is contained in:
Elijah 2026-07-12 11:44:26 -07:00
parent 95af276d2e
commit dad62c9914
21 changed files with 1598 additions and 44 deletions

View file

@ -12,8 +12,8 @@ export default async function ClassLayout(props: LayoutProps<"/[classSlug]">) {
return (
<div className="app-page">
{/* Dynamic Header & Tabs */}
<ClassHeader classSlug={classSlug} className={classData.name} />
{/* Dynamic class header */}
<ClassHeader className={classData.name} />
{/* Page content */}
{props.children}

View file

@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { ActivityBanner } from "@/components/activity/ActivityBanner";
interface ClassItem {
id: string;
@ -56,33 +57,9 @@ export default function HomePage() {
} finally { setSavingEdit(false); }
}
const deckTotal = classes.reduce((sum, item) => sum + item._count.decks, 0);
const quizTotal = classes.reduce((sum, item) => sum + item._count.quizSets, 0);
return (
<div className="app-page">
<section className="relative mb-10 overflow-hidden rounded-[2rem] bg-[#172033] px-6 py-8 text-white shadow-[var(--shadow-card)] sm:px-9 sm:py-10 dark:bg-[#111827]">
<div className="absolute -right-16 -top-24 h-64 w-64 rounded-full bg-primary/70 blur-3xl" />
<div className="absolute -bottom-28 right-1/3 h-52 w-52 rounded-full bg-accent/35 blur-3xl" />
<div className="relative flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between">
<div>
<p className="mb-3 text-xs font-bold uppercase tracking-[0.22em] text-white/55">Personal library</p>
<h1 className="editorial-title max-w-2xl text-4xl leading-[1.02] sm:text-5xl">What are we learning today?</h1>
<p className="mt-4 max-w-xl text-sm leading-6 text-white/65 sm:text-base">Pick up where you left off, sharpen a weak topic, or build something new.</p>
</div>
<button onClick={() => setShowCreate(true)} className="inline-flex min-h-12 items-center justify-center gap-2 self-start rounded-xl bg-white px-5 text-sm font-bold text-[#172033] shadow-lg transition-transform hover:-translate-y-0.5 lg:self-auto">
<span className="text-xl leading-none">+</span> New class
</button>
</div>
<div className="relative mt-8 grid max-w-xl grid-cols-3 gap-3 border-t border-white/12 pt-6">
{[[classes.length, "Classes"], [deckTotal, "Decks"], [quizTotal, "Quizzes"]].map(([value, label]) => (
<div key={label}>
<div className="text-2xl font-extrabold sm:text-3xl">{value}</div>
<div className="mt-1 text-xs font-semibold text-white/50">{label}</div>
</div>
))}
</div>
</section>
<ActivityBanner onNewClass={() => setShowCreate(true)} />
{showCreate && (
<section className="animate-slide-up mb-8 rounded-2xl border border-primary/20 bg-bg-surface p-5 shadow-[var(--shadow-card)] sm:p-6">

View file

@ -0,0 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import { studyActivitySchema } from "@/lib/validation/activitySchemas";
import * as activityService from "@/services/activityService";
export async function GET() {
return NextResponse.json(await activityService.getActivitySummary());
}
export async function POST(request: NextRequest) {
const parsed = studyActivitySchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) {
return NextResponse.json({ error: "Invalid activity type" }, { status: 400 });
}
await activityService.recordActivity(parsed.data.type);
return NextResponse.json({ success: true }, { status: 201 });
}