| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- "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 { normalizeBranchIdDraft } from "@/components/admin/users/usersUi";
- import {
- CREATE_ROLE_OPTIONS,
- EMAIL_RE,
- BRANCH_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" ? normalizeBranchIdDraft(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 (!BRANCH_RE.test(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 effectiveDisabled = Boolean(disabled || isSubmitting);
- 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;
- }
- setIsSubmitting(true);
- try {
- const username = normalizeUsername(form.username);
- const email = normalizeEmail(form.email);
- const role = String(form.role || "").trim();
- const branchId =
- role === "branch" ? normalizeBranchIdDraft(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],
- );
- return {
- open,
- setOpen,
- form,
- setPatch,
- error,
- policyLines,
- isSubmitting,
- effectiveDisabled,
- handleSubmit,
- handleOpenChange,
- };
- }
|