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
This commit is contained in:
Claude
2026-02-08 12:39:18 +00:00
parent 240b10b2d7
commit 41e686c560
52 changed files with 11375 additions and 4 deletions

9
lib/supabase/client.ts Normal file
View File

@@ -0,0 +1,9 @@
import { createBrowserClient } from "@supabase/ssr";
import type { Database } from "@/types/database.types";
// Client Supabase côté navigateur (composants client)
export const createClient = () =>
createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

View File

@@ -0,0 +1,59 @@
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
// Middleware Supabase pour refresh des tokens auth
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
);
supabaseResponse = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
);
},
},
}
);
const {
data: { user },
} = await supabase.auth.getUser();
// Rediriger vers login si pas connecté et route protégée
if (
!user &&
request.nextUrl.pathname.startsWith("/dashboard")
) {
const url = request.nextUrl.clone();
url.pathname = "/login";
return NextResponse.redirect(url);
}
// Rediriger vers dashboard si déjà connecté et sur login/register
if (
user &&
(request.nextUrl.pathname === "/login" ||
request.nextUrl.pathname === "/register")
) {
const url = request.nextUrl.clone();
url.pathname = "/dashboard";
return NextResponse.redirect(url);
}
return supabaseResponse;
}

45
lib/supabase/server.ts Normal file
View File

@@ -0,0 +1,45 @@
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import type { Database } from "@/types/database.types";
// Client Supabase côté serveur (Server Components, Route Handlers)
export const createClient = async () => {
const cookieStore = await cookies();
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// Ignore en Server Component (lecture seule)
}
},
},
}
);
};
// Client admin avec service role (webhooks, opérations admin)
export const createAdminClient = () => {
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
cookies: {
getAll() {
return [];
},
setAll() {},
},
}
);
};