"use client"; import React from "react"; import { adminCreateUser, ApiClientError } from "@/lib/frontend/apiClient"; import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect"; import { notifySuccess, notifyError, notifyApiError, } from "@/lib/frontend/ui/toast"; import { getPasswordPolicyHintLinesDe, reasonsToHintLinesDe, buildWeakPasswordMessageDe, } from "@/lib/frontend/profile/passwordPolicyUi"; import { useSearchBranches } from "@/lib/frontend/search/useSearchBranches"; import { evaluateBranchExistence, normalizeBranchIdInput, isValidBranchIdFormat, } from "@/lib/frontend/admin/users/userManagementUx"; import { CREATE_ROLE_OPTIONS, EMAIL_RE, normalizeUsername, normalizeEmail, } from "@/components/admin/users/create-user/createUserUtils"; const DEFAULT_FORM = Object.freeze({ username: "", email: "", role: "branch", branchId: "", initialPassword: "", }); function cloneDefaultForm() { return { ...DEFAULT_FORM }; } function validateClient(form) { const username = normalizeUsername(form?.username); const email = normalizeEmail(form?.email); const role = String(form?.role || "").trim(); const branchId = role === "branch" ? normalizeBranchIdInput(form?.branchId) : ""; const initialPassword = String(form?.initialPassword || ""); if (!username) return { title: "Benutzername fehlt.", description: null }; if (!email) return { title: "E-Mail fehlt.", description: null }; if (!EMAIL_RE.test(email)) { return { title: "E-Mail ist ungültig.", description: "Bitte prüfen Sie die Eingabe.", }; } const isKnownRole = CREATE_ROLE_OPTIONS.some((x) => x.value === role); if (!role || !isKnownRole) return { title: "Rolle fehlt.", description: null }; if (role === "branch") { if (!branchId) { return { title: "Niederlassung fehlt.", description: "Für Niederlassungs-User ist eine NL erforderlich.", }; } if (!isValidBranchIdFormat(branchId)) { return { title: "Niederlassung ist ungültig.", description: "Format: NL01, NL02, ...", }; } } if (!initialPassword.trim()) { return { title: "Initiales Passwort fehlt.", description: null }; } return null; } function extractDuplicateFields(details) { if (!details || typeof details !== "object") return []; // Accept both shapes: // - { field: "username" } // - { fields: ["username","email"] } const one = typeof details.field === "string" && details.field.trim() ? details.field.trim() : null; const many = Array.isArray(details.fields) ? details.fields.map((x) => String(x)) : null; const list = many ?? (one ? [one] : []); return Array.from(new Set(list.map(String))); } function mapDuplicateFieldsToGermanMessage(fields) { const hasUsername = fields.includes("username"); const hasEmail = fields.includes("email"); if (!hasUsername && !hasEmail) return null; if (hasUsername && hasEmail) { return { title: "Benutzername und E-Mail existieren bereits.", description: "Bitte wählen Sie andere Werte und versuchen Sie es erneut.", }; } if (hasUsername) { return { title: "Benutzername existiert bereits.", description: "Bitte wählen Sie einen anderen Benutzernamen.", }; } return { title: "E-Mail existiert bereits.", description: "Bitte wählen Sie eine andere E-Mail-Adresse.", }; } export function useCreateUserDialog({ disabled = false, onCreated } = {}) { const [open, setOpen] = React.useState(false); const [isSubmitting, setIsSubmitting] = React.useState(false); const [form, setForm] = React.useState(() => cloneDefaultForm()); const [error, setError] = React.useState(null); const policyLines = React.useMemo(() => getPasswordPolicyHintLinesDe(), []); const role = String(form?.role || "branch").trim(); const shouldLoadBranches = open && role === "branch"; const { status: branchesStatus, branches: availableBranchIds } = useSearchBranches({ enabled: shouldLoadBranches }); const effectiveDisabled = Boolean(disabled || isSubmitting); const branchExistence = React.useMemo( () => evaluateBranchExistence({ role, branchId: form?.branchId, branchesStatus, availableBranchIds, }), [role, form?.branchId, branchesStatus, availableBranchIds], ); const canSubmit = !effectiveDisabled && !branchExistence.shouldBlockSubmit; const setPatch = React.useCallback((patch) => { setForm((prev) => ({ ...prev, ...(patch || {}) })); }, []); const resetForm = React.useCallback(() => { setForm(cloneDefaultForm()); setError(null); }, []); const redirectToLoginExpired = React.useCallback(() => { const next = typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : "/admin/users"; window.location.replace( buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }), ); }, []); const handleOpenChange = React.useCallback( (nextOpen) => { setOpen(nextOpen); if (!nextOpen) resetForm(); }, [resetForm], ); const handleSubmit = React.useCallback( async (e) => { e?.preventDefault?.(); if (effectiveDisabled) return; setError(null); const clientErr = validateClient(form); if (clientErr) { setError(clientErr); notifyError({ title: clientErr.title, description: clientErr.description, }); return; } if (branchExistence.shouldBlockSubmit) { const mapped = { title: "Niederlassung existiert nicht.", description: "Die gewählte Niederlassung ist nicht in der aktuellen Liste vorhanden.", }; setError(mapped); notifyError(mapped); return; } setIsSubmitting(true); try { const username = normalizeUsername(form.username); const email = normalizeEmail(form.email); const role = String(form.role || "").trim(); const branchId = role === "branch" ? normalizeBranchIdInput(form.branchId) : null; const initialPassword = String(form.initialPassword || ""); await adminCreateUser({ username, email, role, branchId, initialPassword, }); notifySuccess({ title: "Benutzer angelegt", description: `Benutzer "${username}" wurde erstellt.`, }); setOpen(false); resetForm(); if (typeof onCreated === "function") onCreated(); } catch (err) { if (err instanceof ApiClientError) { if (err.code === "AUTH_UNAUTHENTICATED") { notifyApiError(err); redirectToLoginExpired(); return; } if (err.code === "VALIDATION_WEAK_PASSWORD") { const reasons = err.details?.reasons; const minLength = err.details?.minLength; const hints = reasonsToHintLinesDe({ reasons, minLength }); const description = buildWeakPasswordMessageDe({ reasons, minLength, }); setError({ title: "Passwort ist zu schwach.", description, hints, }); notifyError({ title: "Passwort ist zu schwach.", description }); return; } // Precise duplicate feedback (username/email) if (err.code === "VALIDATION_INVALID_FIELD") { const fields = extractDuplicateFields(err.details); const mapped = mapDuplicateFieldsToGermanMessage(fields); if (mapped) { setError(mapped); notifyError(mapped); return; } } setError({ title: "Benutzer konnte nicht angelegt werden.", description: "Bitte prüfen Sie die Eingaben und versuchen Sie es erneut.", }); notifyApiError(err, { fallbackTitle: "Benutzer konnte nicht angelegt werden.", fallbackDescription: "Bitte prüfen Sie die Eingaben und versuchen Sie es erneut.", }); return; } setError({ title: "Benutzer konnte nicht angelegt werden.", description: "Bitte versuchen Sie es erneut.", }); notifyError({ title: "Benutzer konnte nicht angelegt werden.", description: "Bitte versuchen Sie es erneut.", }); } finally { setIsSubmitting(false); } }, [ effectiveDisabled, form, onCreated, redirectToLoginExpired, resetForm, branchExistence.shouldBlockSubmit, ], ); return { open, setOpen, form, setPatch, error, policyLines, branchesStatus, branchExistence, isSubmitting, effectiveDisabled, canSubmit, handleSubmit, handleOpenChange, }; }