feat: Détecter compte activé + amélioration template email activation
This commit is contained in:
parent
ba727563d5
commit
26579a9407
3 changed files with 148 additions and 4 deletions
|
|
@ -7,10 +7,53 @@ import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
|
|||
export default function ActivateContent() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [status, setStatus] = useState<"loading" | "success" | "error">("loading");
|
||||
const [status, setStatus] = useState<"loading" | "success" | "error" | "already-active">("loading");
|
||||
const [message, setMessage] = useState("");
|
||||
const [hasProcessed, setHasProcessed] = useState(false);
|
||||
|
||||
// Fonction pour vérifier si le compte est déjà actif en utilisant le token
|
||||
const checkIfAccountAlreadyActive = async (accessToken: string | null) => {
|
||||
if (!accessToken) {
|
||||
setStatus("error");
|
||||
setMessage("Lien d'activation invalide ou expiré.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Décoder le JWT pour extraire l'email (sans vérifier la signature)
|
||||
const payload = JSON.parse(atob(accessToken.split('.')[1]));
|
||||
const email = payload.email;
|
||||
|
||||
if (!email) {
|
||||
setStatus("error");
|
||||
setMessage("Lien d'activation invalide ou expiré.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("🔍 [ACTIVATE] Vérification du statut du compte pour:", email);
|
||||
|
||||
// Vérifier si l'utilisateur existe et a déjà signé in via l'API
|
||||
const response = await fetch(`/api/check-user-status?email=${encodeURIComponent(email)}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.exists && data.hasSignedIn) {
|
||||
console.log("✅ [ACTIVATE] Compte déjà activé détecté");
|
||||
setStatus("already-active");
|
||||
setMessage("Votre compte a déjà été activé. Vous pouvez vous connecter.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sinon, message d'erreur standard
|
||||
setStatus("error");
|
||||
setMessage("Lien d'activation invalide ou expiré. Veuillez demander un nouveau lien d'invitation.");
|
||||
} catch (error) {
|
||||
console.log("⚠️ [ACTIVATE] Erreur lors de la vérification du compte:", error);
|
||||
setStatus("error");
|
||||
setMessage("Lien d'activation invalide ou expiré.");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Empêcher le double traitement
|
||||
if (hasProcessed) {
|
||||
|
|
@ -64,6 +107,14 @@ export default function ActivateContent() {
|
|||
|
||||
if (error) {
|
||||
console.log("❌ [ACTIVATE] Erreur lors de l'établissement de la session:", error);
|
||||
|
||||
// Détecter si c'est un lien expiré et vérifier si le compte existe déjà
|
||||
if (error.message.toLowerCase().includes("expired") ||
|
||||
error.message.toLowerCase().includes("invalid")) {
|
||||
await checkIfAccountAlreadyActive(access_token);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("error");
|
||||
setMessage(`Erreur lors de l'activation: ${error.message}`);
|
||||
return;
|
||||
|
|
@ -165,6 +216,17 @@ export default function ActivateContent() {
|
|||
|
||||
if (error) {
|
||||
console.log("❌ [ACTIVATE] Erreur lors de l'acceptation de l'invitation:", error);
|
||||
|
||||
// Détecter si c'est un lien expiré et vérifier si le compte existe déjà
|
||||
if (error.message.toLowerCase().includes("expired") ||
|
||||
error.message.toLowerCase().includes("invalid")) {
|
||||
// Pour l'ancien format, on n'a pas facilement accès à l'email
|
||||
// On affiche juste le message d'erreur standard
|
||||
setStatus("error");
|
||||
setMessage("Lien d'activation invalide ou expiré. Veuillez demander un nouveau lien d'invitation.");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("error");
|
||||
setMessage(`Erreur lors de l'activation: ${error.message}`);
|
||||
return;
|
||||
|
|
@ -268,6 +330,23 @@ export default function ActivateContent() {
|
|||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === "already-active" && (
|
||||
<div className="mt-4">
|
||||
<div className="text-blue-600">
|
||||
<svg className="mx-auto h-12 w-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-gray-900 font-medium">{message}</p>
|
||||
<button
|
||||
onClick={() => router.push("/signin")}
|
||||
className="mt-6 w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
65
app/api/check-user-status/route.ts
Normal file
65
app/api/check-user-status/route.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// app/api/check-user-status/route.ts
|
||||
export const dynamic = "force-dynamic";
|
||||
import { NextResponse } from "next/server";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
/**
|
||||
* Endpoint pour vérifier si un utilisateur existe et a déjà activé son compte
|
||||
* Utilisé par la page d'activation pour détecter les comptes déjà actifs
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const email = searchParams.get("email");
|
||||
|
||||
if (!email) {
|
||||
return NextResponse.json(
|
||||
{ error: "Email requis" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Utiliser le service role pour accéder aux données auth
|
||||
const admin = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{ auth: { autoRefreshToken: false, persistSession: false } }
|
||||
);
|
||||
|
||||
// Rechercher l'utilisateur par email
|
||||
const { data: users, error } = await admin.auth.admin.listUsers();
|
||||
|
||||
if (error) {
|
||||
console.error("❌ Erreur lors de la recherche de l'utilisateur:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Trouver l'utilisateur avec l'email correspondant
|
||||
const user = users.users.find(u => u.email?.toLowerCase() === email.toLowerCase());
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
exists: false,
|
||||
hasSignedIn: false
|
||||
});
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur a déjà signé in
|
||||
const hasSignedIn = !!user.last_sign_in_at;
|
||||
|
||||
return NextResponse.json({
|
||||
exists: true,
|
||||
hasSignedIn
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("❌ Erreur inattendue:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Erreur serveur" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -403,10 +403,10 @@ const EMAIL_TEMPLATES_V2: Record<EmailTypeV2, EmailTemplateV2> = {
|
|||
subject: 'Activez votre compte – {{organizationName}}',
|
||||
title: 'Activez votre compte',
|
||||
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
|
||||
mainMessage: 'Bienvenue ! Vous avez été invité à rejoindre {{organizationName}} sur Espace Paie Odentas.<br><br><span style="color: #64748B; font-size: 14px;">Après validation, vous serez redirigé vers votre compte Odentas. Vous pourrez, si vous le souhaitez, créer un mot de passe depuis la page Sécurité. Si vous ne créez pas de mot de passe, la connexion se fera avec l\'envoi d\'un code par e-mail.</span>',
|
||||
closingMessage: 'Toute l\'équipe Odentas reste à votre disposition si vous avez des questions. <a href="mailto:paie@odentas.fr" style="color:#0B5FFF; text-decoration:none;">Contactez-nous</a> ou répondez à cet e-mail.',
|
||||
mainMessage: 'Bienvenue ! Vous avez été invité à rejoindre {{organizationName}} sur l\'Espace Paie Odentas.<br><br><span style="color: #64748B; font-size: 14px;">Après validation, vous serez redirigé vers votre compte Odentas. Vous pourrez, si vous le souhaitez, créer un mot de passe depuis la page Sécurité. Si vous ne créez pas de mot de passe, la connexion se fera avec l\'envoi d\'un code par e-mail.</span>',
|
||||
closingMessage: 'Toute l\'équipe Odentas reste à votre disposition si vous avez des questions. <a href="mailto:paie@odentas.fr" style="color:#0B5FFF; text-decoration:none;">Contactez-nous</a> ou répondez à cet e-mail.<br><br><span style="color: #64748B; font-size: 13px;">Par mesure de sécurité, nous vous invitons à ne pas transférer cet e-mail.</span>',
|
||||
ctaText: 'Activer mon compte',
|
||||
footerText: 'Vous recevez cet e-mail car un accès vous a été créé sur Espace Paie Odentas.',
|
||||
footerText: 'Vous recevez cet e-mail car un accès vous a été créé sur l\'Espace Paie Odentas, par un administrateur de l\'organisation {{organizationName}}, qui est cliente de Odentas. Si vous estimez avoir reçu cet e-mail par erreur, contactez-nous en répondant à cet e-mail.',
|
||||
preheaderText: 'Activation de votre compte · {{organizationName}} · Activez maintenant',
|
||||
colors: {
|
||||
headerColor: STANDARD_COLORS.HEADER,
|
||||
|
|
|
|||
Loading…
Reference in a new issue