"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; type FormState = "idle" | "submitting" | "success" | "error"; /** * InviteForm — owner submits email + name + role to /api/team/invite. * On success, broadcasts `team:refresh` so the sibling TeamList * re-fetches the member list. * * Form fields mirror the POST body: * { email, givenName, familyName, role: "owner" | "user" } * * Role defaults to "user" — the more conservative grant. Owner * promotion happens in ZITADEL Console for now. */ export function InviteForm() { const t = useTranslations("team"); const tCommon = useTranslations("common"); const [form, setForm] = useState({ email: "", givenName: "", familyName: "", role: "user" as "owner" | "user", }); const [state, setState] = useState("idle"); const [error, setError] = useState(""); function handleChange(e: React.ChangeEvent) { setForm((prev) => ({ ...prev, [e.target.name]: e.target.value })); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(""); setState("submitting"); try { const res = await fetch("/api/team/invite", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form), }); if (!res.ok) { const data = await res.json(); if (data.code === "user_already_exists") { throw new Error(t("inviteUserExists")); } throw new Error(data.error || "Invite failed"); } setState("success"); setForm({ email: "", givenName: "", familyName: "", role: "user" }); // Tell the TeamList sibling to refresh window.dispatchEvent(new Event("team:refresh")); // Auto-clear the success banner after a moment so the form // doesn't permanently look "done" setTimeout(() => setState("idle"), 3500); } catch (err: any) { setError(err.message); setState("error"); } } return (

{t("roleHint")}

{error && (
{error}
)} {state === "success" && (
{t("inviteSent")}
)}
); }