Files
obc-terrassement/app/(dashboard)/dashboard/page.tsx
Claude 41e686c560 feat: complete HookLab MVP - TikTok Shop coaching platform
Full-stack Next.js 15 application with:
- Landing page with marketing components (Hero, Testimonials, Pricing, FAQ)
- Multi-step candidature form with API route
- Stripe Checkout integration (subscription + webhooks)
- Supabase Auth (login/register) with middleware protection
- Dashboard with progress tracking and module system
- Formations pages with completion tracking
- Profile management with password change
- Database schema with RLS policies
- Resend email integration for transactional emails

Stack: Next.js 15, TypeScript, Tailwind CSS v4, Supabase, Stripe, Resend

https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
2026-02-08 12:39:18 +00:00

134 lines
4.3 KiB
TypeScript

import { createClient } from "@/lib/supabase/server";
import Card from "@/components/ui/Card";
import ProgressBar from "@/components/dashboard/ProgressBar";
import ModuleCard from "@/components/dashboard/ModuleCard";
import type { Module, UserProgress, Profile } from "@/types/database.types";
export default async function DashboardPage() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
// Récupérer le profil
const { data: profile } = await supabase
.from("profiles")
.select("*")
.eq("id", user!.id)
.single() as { data: Profile | null };
// Récupérer les modules publiés
const { data: modules } = await supabase
.from("modules")
.select("*")
.eq("is_published", true)
.order("week_number", { ascending: true })
.order("order_index", { ascending: true }) as { data: Module[] | null };
// Récupérer la progression
const { data: progress } = await supabase
.from("user_progress")
.select("*")
.eq("user_id", user!.id) as { data: UserProgress[] | null };
const totalModules = modules?.length || 0;
const completedModules =
progress?.filter((p) => p.completed).length || 0;
const progressPercent =
totalModules > 0 ? (completedModules / totalModules) * 100 : 0;
// Prochain module non complété
const completedIds = new Set(
progress?.filter((p) => p.completed).map((p) => p.module_id)
);
const nextModules =
modules?.filter((m) => !completedIds.has(m.id)).slice(0, 3) || [];
return (
<div className="max-w-6xl">
{/* Header */}
<div className="mb-10">
<h1 className="text-3xl font-bold text-white mb-2">
Bonjour {profile?.full_name?.split(" ")[0] || "!"} 👋
</h1>
<p className="text-white/60">
Voici un apercu de ta progression dans le programme.
</p>
</div>
{/* Stats cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-10">
<Card>
<p className="text-white/40 text-sm mb-1">Progression globale</p>
<p className="text-2xl font-bold text-white mb-3">
{Math.round(progressPercent)}%
</p>
<ProgressBar value={progressPercent} showPercentage={false} />
</Card>
<Card>
<p className="text-white/40 text-sm mb-1">Modules completes</p>
<p className="text-2xl font-bold text-white">
{completedModules}
<span className="text-white/30 text-lg font-normal">
/{totalModules}
</span>
</p>
</Card>
<Card>
<p className="text-white/40 text-sm mb-1">Statut abonnement</p>
<div className="flex items-center gap-2">
<span className="w-2 h-2 bg-success rounded-full" />
<p className="text-success font-semibold">Actif</p>
</div>
{profile?.subscription_end_date && (
<p className="text-white/30 text-xs mt-1">
Jusqu&apos;au{" "}
{new Date(profile.subscription_end_date).toLocaleDateString(
"fr-FR"
)}
</p>
)}
</Card>
</div>
{/* Prochains modules */}
{nextModules.length > 0 && (
<div>
<h2 className="text-xl font-bold text-white mb-4">
Continue ta formation
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{nextModules.map((module) => {
const moduleProgress = progress?.find(
(p) => p.module_id === module.id
);
return (
<ModuleCard
key={module.id}
module={module}
progress={moduleProgress}
/>
);
})}
</div>
</div>
)}
{/* Message si aucun module */}
{totalModules === 0 && (
<Card className="text-center py-12">
<div className="text-4xl mb-4">🚀</div>
<h3 className="text-white font-semibold text-lg mb-2">
Le programme arrive bientot !
</h3>
<p className="text-white/40 text-sm max-w-md mx-auto">
Les modules de formation sont en cours de preparation. Tu seras
notifie des qu&apos;ils seront disponibles.
</p>
</Card>
)}
</div>
);
}