- 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éé'
122 lines
No EOL
4.4 KiB
TypeScript
122 lines
No EOL
4.4 KiB
TypeScript
// components/InviteForm.tsx
|
||
"use client";
|
||
|
||
import { useState } from "react";
|
||
|
||
type Org = { id: string; name: string };
|
||
|
||
export default function InviteForm({ orgs, defaultOrgId, hideOrgSelect }: { orgs: Org[]; defaultOrgId?: string; hideOrgSelect?: boolean }) {
|
||
const [firstName, setFirstName] = useState("");
|
||
const [email, setEmail] = useState("");
|
||
const [orgId, setOrgId] = useState(defaultOrgId || orgs[0]?.id || "");
|
||
const [role, setRole] = useState("ADMIN");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [done, setDone] = useState<null | { email: string; orgName: string; role: string }>(null);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
async function onSubmit(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
setError(null);
|
||
setDone(null);
|
||
setSubmitting(true);
|
||
try {
|
||
const res = await fetch("/api/access/nouveau", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
credentials: "include",
|
||
body: JSON.stringify({ firstName, email, orgId, role }),
|
||
});
|
||
const json = await res.json().catch(() => ({}));
|
||
if (!res.ok) {
|
||
setError(json?.error || "Erreur lors de la création de l’utilisateur");
|
||
} else {
|
||
const resolvedOrgId = orgId || defaultOrgId || orgs[0]?.id || "";
|
||
const orgName = orgs.find((o) => o.id === resolvedOrgId)?.name || "";
|
||
setDone({ email, orgName, role });
|
||
setFirstName("");
|
||
setEmail("");
|
||
}
|
||
} catch (err: any) {
|
||
setError(err?.message || "Erreur réseau");
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={onSubmit} className="space-y-6">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-medium">Prénom</label>
|
||
<input
|
||
className="mt-1 w-full rounded-lg border px-3 py-2"
|
||
value={firstName}
|
||
onChange={(e) => setFirstName(e.target.value)}
|
||
placeholder="Prénom"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium">Adresse e-mail</label>
|
||
<input
|
||
type="email"
|
||
className="mt-1 w-full rounded-lg border px-3 py-2"
|
||
value={email}
|
||
onChange={(e) => setEmail(e.target.value)}
|
||
placeholder="ex: nom@domaine.fr"
|
||
required
|
||
/>
|
||
</div>
|
||
{!hideOrgSelect && (
|
||
<div>
|
||
<label className="block text-sm font-medium">Client (organisation)</label>
|
||
<select
|
||
className="mt-1 w-full rounded-lg border px-3 py-2"
|
||
value={orgId}
|
||
onChange={(e) => setOrgId(e.target.value)}
|
||
required
|
||
>
|
||
{orgs.map((o) => (
|
||
<option key={o.id} value={o.id}>{o.name}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
)}
|
||
<div>
|
||
<label className="block text-sm font-medium">Niveau d’accès</label>
|
||
<select
|
||
className="mt-1 w-full rounded-lg border px-3 py-2"
|
||
value={role}
|
||
onChange={(e) => setRole(e.target.value)}
|
||
required
|
||
>
|
||
{!hideOrgSelect && <option value="SUPER_ADMIN">Super Admin</option>}
|
||
<option value="ADMIN">Admin</option>
|
||
<option value="AGENT">Agent</option>
|
||
<option value="COMPTA">Compta</option>
|
||
</select>
|
||
<p className="mt-1 text-xs text-slate-500">
|
||
Un email d'activation sera envoyé automatiquement avec un lien sécurisé pour activer le compte.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{error && <p className="text-sm text-red-600">{error}</p>}
|
||
{done && (
|
||
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">
|
||
✅ Invitation envoyée à <strong>{done.email}</strong> pour <strong>{done.orgName}</strong> ({done.role})
|
||
</div>
|
||
)}
|
||
|
||
<div className="flex gap-3">
|
||
<button
|
||
type="submit"
|
||
disabled={submitting}
|
||
className="px-4 py-2 rounded-lg bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-60"
|
||
>
|
||
{submitting ? "Envoi en cours…" : "Inviter l'utilisateur"}
|
||
</button>
|
||
<a href={hideOrgSelect ? "/vos-acces" : "/staff/utilisateurs"} className="px-4 py-2 rounded-lg border">Annuler</a>
|
||
</div>
|
||
</form>
|
||
);
|
||
} |