E-commerce
Une boutique en ligne simple : catalogue, panier, paiement réel via Stripe.
RĂ©utilisable vs spĂ©cifique : les Ă©tapes rĂ©utilisable sont valables pour n'importe quel projet de ta stack. Les Ă©tapes spĂ©cifique sont propres Ă cette boutique â adapte le catalogue Ă tes propres produits.
Initialiser le projet Next.js
Un projet Next.js qui dĂ©marre en local, avec Tailwind et shadcn/ui prĂȘts Ă l'emploi.
Prérequis : Node.js installé. Terminal ouvert dans le dossier du projet.
-
Installe pnpm si besoin
npm install -g pnpm
pnpm est le gestionnaire de paquets choisi pour ce projet â plus rapide et plus Ă©conome en espace disque que npm.đ Comment voir le rĂ©sultat : tapepnpm -v. Un numĂ©ro de version s'affiche (ex.9.12.0) â sinon l'installation a Ă©chouĂ©. -
Crée le projet Next.js
pnpm create next-app@latest . --typescript --tailwind --eslint --app --src-dir=false --import-alias "@/*"
Le nom du dossier ne doit pas contenir de majuscules (contrainte npm). S'il en contient, crĂ©e le projet dans un dossier temporaire au nom valide puis dĂ©place tout son contenu dans ton dossier de travail.đ Comment voir le rĂ©sultat : tapels. Tu dois voir apparaĂźtreapp/,package.json,tailwind.config.tsâ la commande a rempli ton dossier de fichiers. -
Initialise shadcn/ui
pnpm dlx shadcn@latest init
Ăa crĂ©ecomponents.jsonetlib/utils.ts, et prĂ©pare Tailwind pour recevoir des composants shadcn/ui prĂȘts Ă copier.đ Comment voir le rĂ©sultat : tapels components.json lib/utils.tsâ les deux fichiers doivent exister, sans message d'erreur « No such file ». -
Crée l'arborescence du projet
mkdir -p lib/auth lib/db lib/validations prisma testsmkdir lib/auth, lib/db, lib/validations, prisma, tests
On sĂ©pare dĂšs le dĂ©part : authentification, accĂšs base de donnĂ©es, schĂ©mas de validation. Ăa Ă©vite le fichier fourre-tout de 2000 lignes plus tard.đ Comment voir le rĂ©sultat : tapels libâ les dossiersauth,db,validationsdoivent apparaĂźtre. -
Initialise Git et fais le premier commit
git init git add -A git commit -m "chore: init projet Next.js"
đ Comment voir le rĂ©sultat : tapegit log --onelineâ une ligne avec ton message de commit doit s'afficher. -
Lance le serveur et vérifie
pnpm dev
Ouvrehttp://localhost:3000. Si tu as une erreur 500 mentionnant un module introuvable (ex.Can't resolve 'xxx') alors qu'il est bien danspackage.json: supprimenode_modules,pnpm-lock.yamlet.next, relancepnpm installpuispnpm dev. C'est presque toujours une installation incomplĂšte, pas un bug de code.đ Comment voir le rĂ©sultat : dans ton navigateur, ouvrelocalhost:3000. Tu dois voir la page d'accueil par dĂ©faut de Next.js. Le terminal afficheReadyet n'affiche pas d'erreur rouge.
Base de donnĂ©es â Prisma + Supabase
Une base PostgreSQL en ligne, avec les tables Produit, Commande et LigneCommande créées via Prisma.
Prérequis : étape 1 validée.
-
CrĂ©e un projet sur SupabaseVa sur supabase.com â New project. Choisis un nom, une rĂ©gion proche, et un mot de passe de base de donnĂ©es â note-le quelque part.đ Comment voir le rĂ©sultat : le tableau de bord Supabase affiche ton projet avec un statut « Active » (aprĂšs ~1-2 minutes).
-
RĂ©cupĂšre l'URL de connexionDans le projet Supabase : Project Settings â Database â Connection string, mode URI. Copie l'URL complĂšte (mot de passe inclus).
-
Installe Prisma
pnpm add prisma --save-dev pnpm add @prisma/client pnpm dlx prisma init
CrĂ©eprisma/schema.prismaet un fichier.envavec une variableDATABASE_URLvide.đ Comment voir le rĂ©sultat : tapels prisma/schema.prismaâ le fichier doit exister. -
Colle l'URL dans
.env# .env DATABASE_URL="postgresql://postgres:[MOT-DE-PASSE]@[HOST]:5432/postgres"
Ce fichier ne doit jamais ĂȘtre commit (dĂ©jĂ dans.gitignorepar dĂ©faut) â il contient un mot de passe. -
Ăcris le schĂ©maDans
prisma/schema.prisma, sous les blocsgenerator/datasourcedĂ©jĂ prĂ©sents, ajoute :model Produit { id String @id @default(cuid()) nom String prix Int // en centimes, pour Ă©viter les erreurs d'arrondi stock Int @default(0) createdAt DateTime @default(now()) } model Commande { id String @id @default(cuid()) clientId String statut String @default("EN_ATTENTE") total Int createdAt DateTime @default(now()) lignes LigneCommande[] } model LigneCommande { id String @id @default(cuid()) commandeId String commande Commande @relation(fields: [commandeId], references: [id]) produitId String quantite Int prixUnitaire Int }Les prix sont stockĂ©s en centimes (entiers) plutĂŽt qu'en euros dĂ©cimaux â Ă©vite les erreurs d'arrondi classiques avec les nombres flottants. -
Applique la migration et vérifie
pnpm dlx prisma migrate dev --name init pnpm dlx prisma studio
migrate devcrĂ©e les tables et gĂ©nĂšre le client Prisma typĂ©.studioouvre une interface web locale pour voir/Ă©diter les donnĂ©es Ă la main.đ Comment voir le rĂ©sultat :prisma studioouvrelocalhost:5555â les tablesProduit,CommandeetLigneCommandedoivent apparaĂźtre dans la liste de gauche.
PiĂšge frĂ©quent : connexion refusĂ©e â vĂ©rifie qu'aucun caractĂšre spĂ©cial du mot de passe (@, #...) n'est mal encodĂ© dans l'URL. Supabase propose un bouton pour copier l'URL dĂ©jĂ encodĂ©e.
Authentification â Better Auth
Des comptes clients â historique de commandes, adresse â et une zone /dashboard protĂ©gĂ©e pour l'admin.
Prérequis : étape 2 validée (schéma Prisma en place).
-
Installe Better Auth
pnpm add better-auth
đ Comment voir le rĂ©sultat :better-authapparaĂźt danspackage.json. -
Crée le client Prisma partagé
touch lib/db/client.tsNew-Item -ItemType File -Force -Path lib/db/client.ts
Le dossierlib/dbexiste dĂ©jĂ depuis l'Ă©tape 1 â il ne manque que le fichier. Ouvre-le et colle ce contenu :// lib/db/client.ts import { PrismaClient } from "@prisma/client"; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;Une instance unique dePrismaClient, rĂ©utilisĂ©e partout viaimport { prisma } from "@/lib/db/client"â Ă©vite d'en recrĂ©er une Ă chaque appel (et les erreurs « too many connections » que ça provoque avec le rechargement Ă chaud de Next.js en dĂ©veloppement). -
Crée la config serveur
touch lib/auth/auth.tsNew-Item -ItemType File -Force -Path lib/auth/auth.ts
// lib/auth/auth.ts import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "@/lib/db/client"; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql" }), emailAndPassword: { enabled: true }, }); -
Ajoute les tables auth au schéma
pnpm dlx @better-auth/cli generate pnpm dlx prisma migrate dev --name add_auth
Better Auth gĂ©nĂšre automatiquement les modĂšlesUser,Session,Accountdansschema.prisma.đ Comment voir le rĂ©sultat : ouvreprisma studioâ les tablesUser,Session,Accountdoivent apparaĂźtre. -
Ajoute le secret dans
.env# .env BETTER_AUTH_SECRET="génÚre une chaßne aléatoire longue" BETTER_AUTH_URL="http://localhost:3000"
-
Crée la route API
mkdir -p "app/api/auth/[...all]" && touch "app/api/auth/[...all]/route.ts"New-Item -ItemType Directory -Force -Path "app/api/auth/[...all]"; New-Item -ItemType File -Force -Path "app/api/auth/[...all]/route.ts"
Le nom de dossier[...all](avec les crochets) est la syntaxe Next.js pour capturer toutes les sous-routes â ne le renomme pas.// app/api/auth/[...all]/route.ts import { auth } from "@/lib/auth/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { GET, POST } = toNextJsHandler(auth);đ Comment voir le rĂ©sultat : relancepnpm devet ouvre un endpoint Better Auth (ex.localhost:3000/api/auth/ok) â tu dois obtenir une rĂ©ponse JSON, pas une erreur 404. -
Crée le client auth
touch lib/auth/client.tsNew-Item -ItemType File -Force -Path lib/auth/client.ts
// lib/auth/client.ts import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient();C'est ce client (cÎté navigateur) que les pages/signupet/loginvont utiliser pour appelersignUp.email(...)etsignIn.email(...). -
Crée les pages d'inscription et de connexion
mkdir -p app/signup app/login && touch app/signup/page.tsx app/login/page.tsxmkdir app/signup, app/login; New-Item -ItemType File -Force -Path app/signup/page.tsx, app/login/page.tsx
// app/signup/page.tsx "use client"; import { useState } from "react"; import { authClient } from "@/lib/auth/client"; export default function SignupPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); await authClient.signUp.email({ email, password, name: email }); } return ( <form onSubmit={handleSubmit} className="p-8 flex flex-col gap-2 max-w-sm"> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <input type="password" placeholder="Mot de passe" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit">CrĂ©er un compte</button> </form> ); }// app/login/page.tsx "use client"; import { useState } from "react"; import { authClient } from "@/lib/auth/client"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); await authClient.signIn.email({ email, password }); } return ( <form onSubmit={handleSubmit} className="p-8 flex flex-col gap-2 max-w-sm"> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <input type="password" placeholder="Mot de passe" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit">Se connecter</button> </form> ); }đ Comment voir le rĂ©sultat : crĂ©e un compte test surlocalhost:3000/signup, puis vĂ©rifie dans Prisma Studio qu'une ligne apparaĂźt dans la tableUser. Reconnecte-toi ensuite depuis/loginavec les mĂȘmes identifiants. -
ProtĂšge
/dashboardtouch middleware.tsNew-Item -ItemType File -Force -Path middleware.ts
// middleware.ts (Ă la racine du projet, Ă cĂŽtĂ© de package.json) import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/lib/auth/auth"; export async function middleware(request: NextRequest) { const session = await auth.api.getSession({ headers: request.headers }); if (!session && request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*"] };Lematcherlimite le middleware aux routes/dashboard/*â il ne s'exĂ©cute pas sur le reste du site.đ Comment voir le rĂ©sultat : en navigation privĂ©e (non connectĂ©), va surlocalhost:3000/dashboardâ tu dois ĂȘtre redirigĂ© vers/login.
PiĂšge frĂ©quent : si la doc officielle de Better Auth diffĂšre de ce qui est Ă©crit ici, suis la doc â c'est une librairie qui Ă©volue vite. Dis-le moi si un nom de fonction a changĂ©, on ajuste.
Paiement â Stripe
Encaisser un vrai paiement carte, en toute sécurité.
Prérequis : étape 3 validée.
-
CrĂ©e un compte Stripe (mode test)Sur stripe.com â le mode test permet de simuler des paiements sans vraie carte bancaire, avec le numĂ©ro
4242 4242 4242 4242. -
Installe le SDK
pnpm add stripe
đ Comment voir le rĂ©sultat :stripeapparaĂźt danspackage.json. -
Ajoute les clés dans
.env# .env STRIPE_SECRET_KEY="sk_test_..." STRIPE_WEBHOOK_SECRET="whsec_..."
Les deux se trouvent dans le tableau de bord Stripe, section Développeurs. -
Crée une session de paiement
mkdir -p lib/stripe && touch lib/stripe/checkout.tsmkdir lib/stripe; New-Item -ItemType File -Force -Path lib/stripe/checkout.ts
// lib/stripe/checkout.ts "use server"; import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); export async function createCheckoutSession(commandeId: string, items: { nom: string; prix: number; quantite: number }[]) { const session = await stripe.checkout.sessions.create({ mode: "payment", line_items: items.map((i) => ({ price_data: { currency: "eur", product_data: { name: i.nom }, unit_amount: i.prix, }, quantity: i.quantite, })), metadata: { commandeId }, success_url: `${process.env.BETTER_AUTH_URL}/commande/succes`, cancel_url: `${process.env.BETTER_AUTH_URL}/panier`, }); return session.url; }metadata: { commandeId }permet au webhook de retrouver quelle commande marquer comme payĂ©e â sans ça, impossible de savoir quel paiement correspond Ă quelle ligne en base.đ Comment voir le rĂ©sultat : se testera concrĂštement une fois la page panier branchĂ©e Ă l'Ă©tape 5 â pour l'instant, vĂ©rifie juste que le fichier ne contient pas d'erreur TypeScript dans ton Ă©diteur. -
Crée la route webhook
mkdir -p app/api/stripe/webhook && touch app/api/stripe/webhook/route.tsmkdir app/api/stripe/webhook; New-Item -ItemType File -Force -Path app/api/stripe/webhook/route.ts
// app/api/stripe/webhook/route.ts import { headers } from "next/headers"; import Stripe from "stripe"; import { prisma } from "@/lib/db/client"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); export async function POST(request: Request) { const body = await request.text(); const signature = (await headers()).get("stripe-signature")!; const event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!); if (event.type === "checkout.session.completed") { const session = event.data.object as Stripe.Checkout.Session; const commandeId = session.metadata?.commandeId; if (commandeId) { await prisma.commande.update({ where: { id: commandeId }, data: { statut: "PAYEE" }, }); } } return new Response(null, { status: 200 }); }Ne marque jamais une commande payĂ©e depuis le navigateur (cĂŽtĂ© client) â seul le webhook, signĂ© par Stripe, fait foi. Sinon n'importe qui peut appeler ton API et se dĂ©clarer « payĂ© » sans payer.session.metadata.commandeIdvient de l'Ă©tape prĂ©cĂ©dente : c'est ce qui cible la bonne commande.đ Comment voir le rĂ©sultat : lancestripe listen --forward-to localhost:3000/api/stripe/webhookdans un terminal sĂ©parĂ© â il doit afficherReady! Your webhook signing secret is whsec_.... Fais un paiement test (carte4242 4242 4242 4242) : ce terminal doit logguer l'Ă©vĂ©nementcheckout.session.completed.
PiĂšge frĂ©quent : en local, Stripe ne peut pas atteindre localhost pour envoyer le webhook â utilise stripe listen --forward-to localhost:3000/api/stripe/webhook (CLI Stripe) pendant le dĂ©veloppement.
Catalogue, panier & commandes
Parcourir les produits, les mettre au panier, passer commande.
Prérequis : étape 4 validée.
-
Installe et crée le store du panier
pnpm add zustand
mkdir -p lib/store && touch lib/store/panier.tsmkdir lib/store; New-Item -ItemType File -Force -Path lib/store/panier.ts
// lib/store/panier.ts import { create } from "zustand"; type ArticlePanier = { id: string; nom: string; prix: number; quantite: number }; type PanierStore = { articles: ArticlePanier[]; ajouter: (produit: { id: string; nom: string; prix: number }) => void; retirer: (id: string) => void; vider: () => void; }; export const usePanier = create<PanierStore>((set) => ({ articles: [], ajouter: (produit) => set((state) => { const existant = state.articles.find((a) => a.id === produit.id); if (existant) { return { articles: state.articles.map((a) => a.id === produit.id ? { ...a, quantite: a.quantite + 1 } : a ), }; } return { articles: [...state.articles, { ...produit, quantite: 1 }] }; }), retirer: (id) => set((state) => ({ articles: state.articles.filter((a) => a.id !== id) })), vider: () => set({ articles: [] }), }));Le panier vit dans le navigateur avant le paiement â pas besoin de base de donnĂ©es pour ça. Zustand garde l'Ă©tat du panier accessible depuis n'importe quel composant, sans tout faire remonter via les props. -
Crée le bouton « Ajouter au panier »
touch components/ajouter-au-panier-button.tsxNew-Item -ItemType File -Force -Path components/ajouter-au-panier-button.tsx
// components/ajouter-au-panier-button.tsx "use client"; import { usePanier } from "@/lib/store/panier"; export default function AjouterAuPanierButton({ produit }: { produit: { id: string; nom: string; prix: number } }) { const ajouter = usePanier((state) => state.ajouter); return <button onClick={() => ajouter(produit)}>Ajouter au panier</button>; }Ce bouton doit ĂȘtre un composant client Ă part ("use client") carusePanierutilise du state du navigateur â la page catalogue, elle, peut rester un Server Component qui lit Prisma directement. -
Page catalogue
mkdir -p app/produits && touch app/produits/page.tsxmkdir app/produits; New-Item -ItemType File -Force -Path app/produits/page.tsx
// app/produits/page.tsx import { prisma } from "@/lib/db/client"; import AjouterAuPanierButton from "@/components/ajouter-au-panier-button"; export default async function ProduitsPage() { const produits = await prisma.produit.findMany(); return ( <main className="p-8 grid grid-cols-1 sm:grid-cols-3 gap-4"> {produits.map((p) => ( <div key={p.id} className="border rounded-lg p-4"> <h2 className="font-bold">{p.nom}</h2> <p>{(p.prix / 100).toFixed(2)} âŹ</p> <AjouterAuPanierButton produit={{ id: p.id, nom: p.nom, prix: p.prix }} /> </div> ))} </main> ); }đ Comment voir le rĂ©sultat : ouvrelocalhost:3000/produitsâ les produits que tu as ajoutĂ©s dans Prisma Studio doivent s'afficher, un par carte, avec leur prix converti en euros. -
Page panier et commande avant paiement
mkdir -p app/panier && touch app/panier/page.tsx app/panier/actions.tsmkdir app/panier; New-Item -ItemType File -Force -Path app/panier/page.tsx, app/panier/actions.ts
// app/panier/actions.ts "use server"; import { prisma } from "@/lib/db/client"; import { createCheckoutSession } from "@/lib/stripe/checkout"; type ArticlePanier = { id: string; nom: string; prix: number; quantite: number }; export async function creerCommandeEtPayer(articles: ArticlePanier[]) { const total = articles.reduce((sum, a) => sum + a.prix * a.quantite, 0); const commande = await prisma.commande.create({ data: { clientId: "TODO: remplace par l'id de l'utilisateur connectĂ© (session Better Auth)", statut: "EN_ATTENTE", total, lignes: { create: articles.map((a) => ({ produitId: a.id, quantite: a.quantite, prixUnitaire: a.prix, })), }, }, }); return createCheckoutSession(commande.id, articles); }// app/panier/page.tsx "use client"; import { usePanier } from "@/lib/store/panier"; import { creerCommandeEtPayer } from "./actions"; export default function PanierPage() { const { articles, retirer } = usePanier(); const total = articles.reduce((sum, a) => sum + a.prix * a.quantite, 0); async function handlePayer() { const url = await creerCommandeEtPayer(articles); window.location.href = url; } return ( <main className="p-8"> <h1 className="text-2xl font-bold mb-4">Panier</h1> <ul> {articles.map((a) => ( <li key={a.id} className="flex justify-between gap-4"> <span>{a.nom} Ă {a.quantite}</span> <span>{((a.prix * a.quantite) / 100).toFixed(2)} âŹ</span> <button onClick={() => retirer(a.id)}>Retirer</button> </li> ))} </ul> <p className="mt-4 font-bold">Total : {(total / 100).toFixed(2)} âŹ</p> <button onClick={handlePayer} disabled={articles.length === 0}>Payer</button> </main> ); }Remplace leTODOdeclientIdpar l'id rĂ©cupĂ©rĂ© viaauth.api.getSession(...)(Ă©tape 3) une fois l'utilisateur connectĂ© â sans ça, aucune commande ne peut ĂȘtre reliĂ©e Ă un client.creerCommandeEtPayercrĂ©e d'abord laCommandeen base avec statutEN_ATTENTE, puis redirige vers Stripe â le webhook de l'Ă©tape 4 la fera ensuite passer ĂPAYEE.đ Comment voir le rĂ©sultat : depuis le catalogue, ajoute 2-3 produits au panier puis ouvrelocalhost:3000/panierâ ils doivent y ĂȘtre listĂ©s avec le bon total. Clique sur « Payer » : juste avant la redirection vers Stripe, une nouvelle ligneEN_ATTENTEdoit apparaĂźtre dans la tableCommande(Prisma Studio).
Habillage â Tailwind & shadcn/ui
Un rendu visuel cohérent sur tout le site, et une méthode simple pour le modifier plus tard.
Prérequis : étape 5 validée.
-
Comprends oĂč vit le styleIl n'y a pas un fichier
.csspar page. Deux niveaux seulement : les variables de thĂšme globales dansapp/globals.css(couleurs, arrondi â gĂ©nĂ©rĂ©es parshadcn initĂ l'Ă©tape 1, chargĂ©es une seule fois parapp/layout.tsx), et les classes Tailwind Ă©crites directement dans leclassNamede chaque composant. -
Personnalise les couleurs du thĂšmeOuvre
app/globals.cssâ repĂšre le bloc:root { ... }gĂ©nĂ©rĂ© parshadcn init, qui contient des variables comme--primaryou--background. Change leurs valeurs directement dans ce fichier.đ Comment voir le rĂ©sultat : modifie la valeur de--primary, enregistre, rechargelocalhost:3000â n'importe quel composant shadcn/ui dĂ©jĂ installĂ© doit changer de couleur automatiquement, sans toucher au code des pages. -
Crée une mise en page cohérente, réutilisée sur chaque page
touch components/container.tsxNew-Item -ItemType File -Force -Path components/container.tsx
// components/container.tsx export default function Container({ children }: { children: React.ReactNode }) { return ( <div className="max-w-5xl mx-auto px-4 py-12"> {children} </div> ); }Sans ça, tu rĂ©pĂštes les mĂȘmes classes d'espacement (max-w-5xl mx-auto px-4 py-12) dans chaque fichierpage.tsx(catalogue, panier...) â un seul endroit Ă modifier si tu changes la largeur ou les marges du site.đ Comment voir le rĂ©sultat : enveloppe le contenu retournĂ© parapp/page.tsx, la page catalogue et la page panier avec<Container>...</Container>â chaque page doit s'afficher centrĂ©e, avec la mĂȘme marge, une fois rechargĂ©e. -
Remplace le HTML brut par les composants shadcn/ui déjà installés
// components/ajouter-au-panier-button.tsx â avant <button onClick={() => ajouter(produit)}>Ajouter au panier</button> // aprĂšs import { Button } from "@/components/ui/button"; <Button onClick={() => ajouter(produit)}>Ajouter au panier</Button>
Le composantButtonhĂ©rite automatiquement des variables de thĂšme dĂ©finies plus haut â un<button>HTML brut, non. Fais le mĂȘme remplacement pour le bouton « Retirer » et le bouton « Payer » du panier (Ă©tape 5). -
Pour modifier le style plus tardGarde
pnpm devlancĂ© en continu â le rechargement Ă chaud applique tes changements dans le navigateur dĂšs que tu sauvegardes, sans redĂ©marrer le serveur. Ouvre le fichier de la page ou du composant concernĂ©, change les classes dansclassName, enregistre. Pour ajouter un nouveau composant shadcn/ui :pnpm dlx shadcn@latest add <nom>(liste sur ui.shadcn.com). Pour savoir ce que fait une classe (p-4,rounded-lg,text-xl...), consulte le lexique ou tailwindcss.com/docs.
Tests â Vitest & Playwright
Automatiser la vérification du parcours critique.
Prérequis : étapes 4 et 5 validées.
-
Installe et configure Vitest
pnpm add -D vitest
Ăcris un test danstests/panier.validation.test.tsqui vĂ©rifie quecalculTotalPaniercalcule correctement le total du panier avec plusieurs articles. -
Installe et configure Playwright
pnpm create playwright
RĂ©pond aux questions de l'installateur (TypeScript, dossiertests-e2e). Ăcris un test qui : crĂ©e un compte â ajoute un produit au panier â paie avec une carte de test Stripe â vĂ©rifie que la commande apparaĂźt en base. -
Lance les tests
pnpm exec vitest run pnpm exec playwright test
đ Comment voir le rĂ©sultat : le terminal affiche un rĂ©sumĂ© avec un nombre de tests « passed » en vert. Playwright peut aussi ouvrir un rapport HTML (pnpm exec playwright show-report) qui montre le parcours Ă©tape par Ă©tape, avec captures d'Ă©cran si un test Ă©choue.
DĂ©ploiement â Vercel
Une URL publique et fonctionnelle.
Prérequis : étape 7 validée.
-
Ajoute la génération Prisma au build
// package.json "scripts": { "postinstall": "prisma generate" }Sans ça, le build Vercel plante â le client Prisma doit ĂȘtre rĂ©gĂ©nĂ©rĂ© aprĂšs chaque install en production. -
Pousse le code sur GitHub
git remote add origin URL-DE-TON-DEPOT git push -u origin masterđ Comment voir le rĂ©sultat : rafraĂźchis la page du dĂ©pĂŽt sur GitHub â tes fichiers doivent y apparaĂźtre. -
Importe le projet sur VercelSur vercel.com â Add New â Project â sĂ©lectionne le dĂ©pĂŽt GitHub.đ Comment voir le rĂ©sultat : le tableau de bord Vercel passe de « Building » à « Ready », avec un lien de prĂ©visualisation cliquable.
-
Renseigne les variables d'environnementCopie
DATABASE_URL,BETTER_AUTH_SECRET,BETTER_AUTH_URL(URL Vercel finale),STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETdans Settings â Environment Variables.đ Comment voir le rĂ©sultat : pas d'effet visible immĂ©diatement â redĂ©ploie (« Redeploy ») puis teste Ă l'Ă©tape suivante. -
Configure le webhook Stripe en productionDans le tableau de bord Stripe : ajoute un endpoint pointant vers
https://tondomaine.vercel.app/api/stripe/webhookâ l'URL locale utilisĂ©e avec la CLI ne fonctionne plus une fois dĂ©ployĂ©.đ Comment voir le rĂ©sultat : dans le tableau de bord Stripe, section Webhooks, l'endpoint doit afficher un statut vert « Enabled ». -
DĂ©ploie et vĂ©rifie en ligneOuvre l'URL fournie par Vercel, refais le parcours critique Ă la main : crĂ©er un compte â ajouter un produit au panier â payer avec une carte de test â vĂ©rifier la commande.đ Comment voir le rĂ©sultat : sur l'URL
*.vercel.app, un paiement test complet doit faire passer la commande enPAYEEâ vĂ©rifiable dans Prisma Studio ou directement dans Supabase.