import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; import { useTranslation } from "react-i18next"; import { assignRoleToUser, linkOIDCToPasswordAccount, removeRoleFromUser, unlinkOIDCFromPasswordAccount, } from "@/main-axios"; import type { Role, UserRole } from "@/main-axios"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/dialog"; import { AlertCircle, Eye, EyeOff, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { AdminToggle } from "./AdminSettingsShared"; import type { AdminUser } from "./AdminManagementSections"; type ApiErrorLike = { response?: { data?: { error?: string; }; }; message?: string; }; function apiErrorMessage(error: unknown, fallback: string) { const err = error as ApiErrorLike; return err.response?.data?.error || err.message || fallback; } type CreateUserDialogProps = { open: boolean; onOpenChange: Dispatch>; newUsername: string; setNewUsername: Dispatch>; newPassword: string; setNewPassword: Dispatch>; showNewPassword: boolean; setShowNewPassword: Dispatch>; handleCreateUser: () => void; createUserLoading: boolean; }; export function AdminCreateUserDialog({ open, onOpenChange, newUsername, setNewUsername, newPassword, setNewPassword, showNewPassword, setShowNewPassword, handleCreateUser, createUserLoading, }: CreateUserDialogProps) { const { t } = useTranslation(); return ( {t("admin.createUserTitle")} {t("admin.createUserDesc")}
setNewUsername(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleCreateUser()} />
setNewPassword(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleCreateUser()} className="pr-9" />
{t("admin.createUserPasswordHint")}
); } type EditUserDialogProps = { open: boolean; onOpenChange: Dispatch>; editUserTarget: AdminUser | null; editUserLoading: boolean; editUserRoles: UserRole[]; editUserRolesLoading: boolean; roles: Role[]; setEditUserRoles: Dispatch>; handleToggleAdmin: (user: AdminUser) => void; handleRevokeUserSessions: (userId: string) => void; handleDeleteEditUser: () => void; }; export function AdminEditUserDialog({ open, onOpenChange, editUserTarget, editUserLoading, editUserRoles, editUserRolesLoading, roles, setEditUserRoles, handleToggleAdmin, handleRevokeUserSessions, handleDeleteEditUser, }: EditUserDialogProps) { const { t } = useTranslation(); return ( {t("admin.editUserTitle", { username: editUserTarget?.username })} {t("admin.editUserDesc")} {editUserTarget && (
{t("admin.editUserUsername")} {editUserTarget.username}
{t("admin.editUserAuthType")} {editUserTarget.isOidc && editUserTarget.passwordHash ? t("admin.authTypeDual") : editUserTarget.isOidc ? t("admin.authTypeOidc") : t("admin.authTypeLocal")}
{t("admin.editUserAdminStatus")} {editUserTarget.isAdmin ? t("admin.adminStatusAdministrator") : t("admin.adminStatusRegularUser")}
{t("admin.editUserUserId")} {editUserTarget.id}
{t("admin.userAdminAccess")} {t("admin.userAdminAccessDesc")}
handleToggleAdmin(editUserTarget)} />
{t("admin.userRoles")} {editUserRolesLoading ? ( {t("newUi.sidebar.snippets.loading")} ) : ( <> {editUserRoles.length > 0 && (
{editUserRoles.map((ur) => { const roleInfo = roles.find((r) => r.id === ur.roleId); const isSystem = roleInfo?.isSystem ?? false; return ( {ur.roleDisplayName} {!isSystem && ( )} ); })}
)} {roles.filter( (r) => !r.isSystem && !editUserRoles.some((ur) => ur.roleId === r.id), ).length > 0 && (
{t("admin.addRole")}
{roles .filter( (r) => !r.isSystem && !editUserRoles.some((ur) => ur.roleId === r.id), ) .map((r) => ( ))}
)} {editUserRoles.length === 0 && roles.filter((r) => !r.isSystem).length === 0 && ( {t("admin.noCustomRoles")} )} )}
{t("admin.revokeAllUserSessions")} {t("admin.revokeAllUserSessionsDesc")}
{t("admin.deleteUserWarning")}
)}
); } type UnlinkAccountDialogProps = { open: boolean; onOpenChange: Dispatch>; unlinkAccountTarget: { id: string; username: string } | null; onSuccess: (userId: string) => void; }; export function AdminUnlinkAccountDialog({ open, onOpenChange, unlinkAccountTarget, onSuccess, }: UnlinkAccountDialogProps) { const { t } = useTranslation(); const [submitting, setSubmitting] = useState(false); const handleSubmit = async () => { if (!unlinkAccountTarget) return; setSubmitting(true); try { await unlinkOIDCFromPasswordAccount(unlinkAccountTarget.id); toast.success(t("admin.unlinkAccountSuccess")); onSuccess(unlinkAccountTarget.id); onOpenChange(false); } catch (error: unknown) { toast.error(apiErrorMessage(error, t("admin.unlinkAccountFailed"))); } finally { setSubmitting(false); } }; return ( {t("admin.unlinkAccountTitle")} {t("admin.unlinkAccountDesc", { username: unlinkAccountTarget?.username, })}
{t("admin.unlinkAccountWarning")}
); } type LinkAccountDialogProps = { open: boolean; onOpenChange: Dispatch>; linkAccountTarget: { id: string; username: string; isOidc: boolean } | null; setUsers: Dispatch>; users: AdminUser[]; }; export function AdminLinkAccountDialog({ open, onOpenChange, linkAccountTarget, setUsers, users, }: LinkAccountDialogProps) { const { t } = useTranslation(); const [otherUsername, setOtherUsername] = useState(""); const [submitting, setSubmitting] = useState(false); useEffect(() => { if (open) setOtherUsername(""); }, [open, linkAccountTarget]); const isOidcInitiator = linkAccountTarget?.isOidc ?? true; const handleSubmit = async () => { const trimmed = otherUsername.trim(); if (!linkAccountTarget || !trimmed) return; setSubmitting(true); try { if (isOidcInitiator) { await linkOIDCToPasswordAccount(linkAccountTarget.id, trimmed); setUsers((prev) => prev.filter((u) => u.id !== linkAccountTarget.id)); } else { const oidcUser = users.find( (u) => u.username === trimmed && u.isOidc && !u.passwordHash, ); if (!oidcUser) { toast.error(t("admin.linkAccountOidcNotFound")); return; } await linkOIDCToPasswordAccount( oidcUser.id, linkAccountTarget.username, ); setUsers((prev) => prev.filter((u) => u.id !== oidcUser.id)); } toast.success(t("admin.linkAccountSuccess", { username: trimmed })); setOtherUsername(""); onOpenChange(false); } catch (error: unknown) { toast.error(apiErrorMessage(error, t("admin.linkAccountFailed"))); } finally { setSubmitting(false); } }; return ( {t("admin.linkAccountTitle")} {isOidcInitiator ? t("admin.linkAccountDesc", { username: linkAccountTarget?.username, }) : t("admin.linkAccountDescLocal", { username: linkAccountTarget?.username, })}
{t("admin.linkAccountWarningTitle")}
  • {t("admin.linkAccountEffect1")}
  • {t("admin.linkAccountEffect2")}
  • {t("admin.linkAccountEffect3")}
setOtherUsername(e.target.value)} placeholder={ isOidcInitiator ? t("admin.linkAccountTargetPlaceholder") : t("admin.linkAccountOidcPlaceholder") } autoFocus disabled={submitting} />
); }