- Créé sendInvitationWithActivationEmail() pour unifier les invitations - Modifié /api/staff/users/invite pour utiliser generateLink + email - Modifié /api/access/nouveau pour envoyer email d'activation - Modifié /api/access POST pour remplacer pending_invites par système direct - Template account-activation mis à jour : * Titre 'Activez votre compte' * Encart avec infos : invitant (statut), organisation, niveau d'accès * Message de contact formaté comme autres emails * Renommage 'Odentas Paie' → 'Espace Paie Odentas' - Fix page /activate : délai 100ms pour hash fragment + redirection 1s - Liens d'activation forcés vers paie.odentas.fr (tests depuis localhost) - Messages UI cohérents : 'Invitation envoyée' au lieu de 'Compte créé'
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { cookies } from "next/headers";
|
|
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
import { sendInvitationWithActivationEmail } from "@/lib/emailMigrationHelpers";
|
|
|
|
const ROLES = ["SUPER_ADMIN","ADMIN","AGENT","COMPTA"] as const;
|
|
type Role = typeof ROLES[number];
|
|
|
|
export async function POST(req: Request) {
|
|
try {
|
|
const sb = createRouteHandlerClient({ cookies });
|
|
const { data: { user } } = await sb.auth.getUser();
|
|
if (!user) return new NextResponse("Unauthorized", { status: 401 });
|
|
|
|
const { data: staff } = await sb
|
|
.from("staff_users")
|
|
.select("is_staff")
|
|
.eq("user_id", user.id)
|
|
.maybeSingle();
|
|
|
|
if (!staff?.is_staff) return new NextResponse("Forbidden", { status: 403 });
|
|
|
|
const body = await req.json();
|
|
const email = String(body.email || "").trim().toLowerCase();
|
|
const firstName = String(body.firstName || "").trim();
|
|
const orgId = String(body.orgId || "").trim();
|
|
const role = String(body.role || "").trim().toUpperCase() as Role;
|
|
|
|
if (!email || !orgId || !ROLES.includes(role)) {
|
|
return NextResponse.json({ error: "invalid_input" }, { status: 400 });
|
|
}
|
|
|
|
const { data: org } = await sb
|
|
.from("organizations")
|
|
.select("id,name,structure_api")
|
|
.eq("id", orgId)
|
|
.maybeSingle();
|
|
if (!org) return NextResponse.json({ error: "org_not_found" }, { status: 404 });
|
|
|
|
const admin = createClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
|
{ auth: { autoRefreshToken: false, persistSession: false } }
|
|
);
|
|
|
|
const origin = process.env.NEXT_PUBLIC_BASE_URL || "https://paie.odentas.fr";
|
|
|
|
const { data: linkData, error: linkError } = await admin.auth.admin.generateLink({
|
|
type: "invite",
|
|
email,
|
|
options: {
|
|
redirectTo: `${origin}/activate`,
|
|
data: {
|
|
first_name: firstName || null,
|
|
role,
|
|
org_id: org.id,
|
|
org_name: org.name,
|
|
structure_api: org.structure_api ?? null,
|
|
},
|
|
}
|
|
});
|
|
|
|
if (linkError) {
|
|
return NextResponse.json({ error: "link_generation_failed", message: linkError.message }, { status: 400 });
|
|
}
|
|
|
|
const actionLink = linkData?.properties?.action_link;
|
|
const newUserId = linkData?.user?.id;
|
|
|
|
if (!newUserId || !actionLink) {
|
|
return NextResponse.json({ error: "invite_failed" }, { status: 400 });
|
|
}
|
|
|
|
const { error: memberErr } = await admin
|
|
.from("organization_members")
|
|
.insert({ org_id: orgId, user_id: newUserId, role });
|
|
if (memberErr) {
|
|
return NextResponse.json({ error: memberErr.message }, { status: 400 });
|
|
}
|
|
|
|
await admin
|
|
.from("user_auth_prefs")
|
|
.upsert({ user_id: newUserId, allow_magic_link: true, allow_password: false });
|
|
|
|
try {
|
|
const inviterName = (user.user_metadata as any)?.first_name || user.email || 'Administrateur';
|
|
const inviterStatus = staff?.is_staff ? 'Staff' : 'Utilisateur';
|
|
|
|
// Forcer le domaine production dans le lien d'activation pour les tests depuis localhost
|
|
const productionLink = actionLink.replace(/http:\/\/localhost:\d+/, 'https://paie.odentas.fr');
|
|
|
|
await sendInvitationWithActivationEmail(email, {
|
|
firstName: firstName || undefined,
|
|
organizationName: org.name,
|
|
activationUrl: productionLink,
|
|
role,
|
|
inviterName,
|
|
inviterStatus,
|
|
});
|
|
} catch (emailErr) {
|
|
console.warn('⚠️ Envoi email invitation échoué (non-bloquant):', (emailErr as any)?.message || emailErr);
|
|
}
|
|
|
|
return NextResponse.json({
|
|
ok: true,
|
|
user_id: newUserId,
|
|
email,
|
|
org: { id: org.id, name: org.name },
|
|
role,
|
|
});
|
|
} catch (e: any) {
|
|
return new NextResponse(e?.message || "Internal Server Error", { status: 500 });
|
|
}
|
|
}
|