From a6f32dd77a60e7ab5b6c1c7d1416ae6027cc9f7f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 19:43:05 +0000 Subject: [PATCH] feat: branded HookLab approval email + decouple email from Stripe - Email sending now independent from Stripe payment link generation - Professional dark-themed HTML email template matching HookLab branding - Return emailSent/emailError/stripeError status in API response - Admin UI shows detailed status after approve action - Default to onboarding@resend.dev when no custom domain https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y --- app/admin/candidatures/page.tsx | 9 ++ .../admin/candidatures/[id]/approve/route.ts | 140 +++++++++++++----- 2 files changed, 112 insertions(+), 37 deletions(-) diff --git a/app/admin/candidatures/page.tsx b/app/admin/candidatures/page.tsx index 08ec1d0..521dc75 100644 --- a/app/admin/candidatures/page.tsx +++ b/app/admin/candidatures/page.tsx @@ -62,6 +62,15 @@ export default function AdminCandidaturesPage() { setCheckoutUrls((prev) => ({ ...prev, [id]: data.checkoutUrl })); } + // Afficher le statut détaillé + const msgs: string[] = []; + if (data.emailSent) msgs.push("Email envoyé !"); + if (data.emailError) msgs.push("Email : " + data.emailError); + if (data.stripeError) msgs.push("Stripe : " + data.stripeError); + if (msgs.length > 0) { + setError(msgs.join(" | ")); + } + await fetchCandidatures(); } catch (err) { setError(err instanceof Error ? err.message : "Erreur"); diff --git a/app/api/admin/candidatures/[id]/approve/route.ts b/app/api/admin/candidatures/[id]/approve/route.ts index 8f771aa..c63ddde 100644 --- a/app/api/admin/candidatures/[id]/approve/route.ts +++ b/app/api/admin/candidatures/[id]/approve/route.ts @@ -30,6 +30,10 @@ export async function POST( return NextResponse.json({ error: "Candidature introuvable." }, { status: 404 }); } + const email = (candidature as Record).email as string; + const firstname = (candidature as Record).firstname as string; + const candidatureId = (candidature as Record).id as string; + // Mettre à jour le statut const { error: updateError } = await supabase .from("candidatures") @@ -42,13 +46,12 @@ export async function POST( // Générer le lien de paiement Stripe let checkoutUrl: string | null = null; + let stripeError: string | null = null; + if (process.env.STRIPE_SECRET_KEY && process.env.STRIPE_PRICE_ID) { try { const baseUrl = getBaseUrl(); - const email = (candidature as Record).email as string; - const candidatureId = (candidature as Record).id as string; - // Créer ou récupérer le customer Stripe const customers = await stripe.customers.list({ email, limit: 1 }); let customerId: string; if (customers.data.length > 0) { @@ -74,47 +77,110 @@ export async function POST( }); checkoutUrl = session.url; + } catch (err) { + stripeError = err instanceof Error ? err.message : "Erreur Stripe inconnue"; + console.error("Erreur Stripe:", err); + } + } else { + stripeError = "STRIPE_SECRET_KEY ou STRIPE_PRICE_ID non configuré."; + } - // Envoyer le lien par email si Resend est configuré - if (process.env.RESEND_API_KEY && process.env.RESEND_API_KEY !== "re_your-api-key") { - try { - const { Resend } = await import("resend"); - const resend = new Resend(process.env.RESEND_API_KEY); - const firstname = (candidature as Record).firstname as string; + // Envoyer l'email (indépendamment de Stripe) + let emailSent = false; + let emailError: string | null = null; - await resend.emails.send({ - from: process.env.RESEND_FROM_EMAIL || "HookLab ", - to: email, - subject: "Ta candidature HookLab est acceptée !", - html: ` -
-

Félicitations ${firstname} !

-

Ta candidature au programme HookLab a été acceptée !

-

Pour finaliser ton inscription et accéder au programme, clique sur le bouton ci-dessous :

- -

Le paiement est sécurisé via Stripe. Tu peux payer en 2 mensualités de 490€.

-

À très vite,
L'équipe HookLab

-
- `, - }); - } catch (emailError) { - console.error("Erreur envoi email approbation:", emailError); - } - } - } catch (stripeError) { - console.error("Erreur Stripe:", stripeError); + if (!process.env.RESEND_API_KEY) { + emailError = "RESEND_API_KEY non configuré sur Vercel."; + } else { + try { + const { Resend } = await import("resend"); + const resend = new Resend(process.env.RESEND_API_KEY); + const fromEmail = process.env.RESEND_FROM_EMAIL || "HookLab "; + + const paymentButton = checkoutUrl + ? `Finaliser mon inscription` + : `

Le lien de paiement sera envoyé séparément.

`; + + await resend.emails.send({ + from: fromEmail, + to: email, + subject: `${firstname}, ta candidature HookLab est acceptée !`, + html: ` + + + + +
+ + +
+
H
+ HookLab +
+ + +
+

Félicitations ${firstname} !

+

Ta candidature a été acceptée

+ +

+ On a étudié ton profil et on pense que tu as le potentiel pour réussir sur TikTok Shop. +

+

+ Pour accéder au programme et commencer ta formation, il te reste une dernière étape : +

+ + +
+ ${paymentButton} +
+ + +
+

Ce qui t'attend :

+ + + + + +
Programme complet de 8 semaines
Accompagnement personnalisé
Accès à la communauté HookLab
Stratégies TikTok Shop éprouvées
+
+ +

+ Le paiement est 100% sécurisé via Stripe. Tu peux payer en 2 mensualités de 490€. + Si tu as des questions, réponds directement à cet email. +

+
+ + +
+

HookLab - Programme TikTok Shop

+

Enguerrand Ozano · SIREN 994538932

+
+ +
+ + + `, + }); + + emailSent = true; + } catch (err) { + emailError = err instanceof Error ? err.message : "Erreur envoi email"; + console.error("Erreur envoi email approbation:", err); } } return NextResponse.json({ success: true, checkoutUrl, - message: checkoutUrl - ? "Candidature approuvée. Lien de paiement généré." - : "Candidature approuvée. Stripe non configuré, pas de lien de paiement.", + emailSent, + emailError, + stripeError, + message: [ + "Candidature approuvée.", + checkoutUrl ? "Lien de paiement généré." : (stripeError || "Stripe non configuré."), + emailSent ? "Email envoyé." : (emailError || "Email non envoyé."), + ].join(" "), }); }