Add study activity tracking and refresh class header UI
All checks were successful
Automated Container Build / build-and-push (push) Successful in 54s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 54s
This commit is contained in:
parent
95af276d2e
commit
dad62c9914
21 changed files with 1598 additions and 44 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
17
src/app/api/activity/route.ts
Normal file
17
src/app/api/activity/route.ts
Normal 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 });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue