import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Alert, Box, Button, Checkbox, Chip, FormControlLabel, Paper, Stack, TextField, Tooltip, Typography, } from "@mui/material"; import useMediaQuery from "@mui/material/useMediaQuery"; import { DataGrid, GridColDef } from "@mui/x-data-grid"; import { api, getApiErrorMessage } from "../api"; import { useToast } from "../toast"; import { useDialogActions } from "../dialogs"; import { useI18n } from "../i18n/I18nProvider"; type UserDto = { id: string; email?: string | null; userName?: string | null; emailConfirmed: boolean; roles: string[]; isCurrentUser: boolean; canRemoveAdmin: boolean; }; export default function AdminUsersPage() { const isMobile = useMediaQuery("(max-width:767.95px)"); const { toast } = useToast(); const { confirmAction } = useDialogActions(); const { t } = useI18n(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [newEmail, setNewEmail] = useState(""); const [newPassword, setNewPassword] = useState(""); const [newIsAdmin, setNewIsAdmin] = useState(false); async function load() { setLoading(true); try { const res = await api.get("/users"); setUsers(res.data ?? []); } catch { setUsers([]); } finally { setLoading(false); } } useEffect(() => { void load(); }, []); const canCreate = useMemo(() => newEmail.trim().length > 3 && newPassword.length >= 6, [newEmail, newPassword]); const rows = useMemo(() => users.map((u) => ({ id: u.id, email: u.email || "", userName: u.userName || "", roles: u.roles || [], emailConfirmed: u.emailConfirmed, raw: u, })), [users]); const setAdminRole = useCallback(async (u: UserDto, grantAdmin: boolean) => { if (!grantAdmin) { const name = u.userName || u.email || u.id; const confirmed = await confirmAction( u.isCurrentUser ? t("adminUsersSelfDemoteWarning") : t("adminUsersDemoteWarning", { name }), { title: u.isCurrentUser ? t("adminUsersSelfDemoteTitle") : t("adminUsersDemoteTitle"), confirmLabel: t("adminUsersRemoveAdmin"), destructive: true, }, ); if (!confirmed) return; } try { const roles = grantAdmin ? Array.from(new Set([...(u.roles || []), "Admin"])) : (u.roles || []).filter((role) => role.toLowerCase() !== "admin"); await api.put(`/users/${u.id}/roles`, { roles }); toast(t("adminUsersRolesUpdated"), "success"); await load(); } catch (e) { toast(getApiErrorMessage(e, t("adminUsersRolesUpdateFailed")), "error"); } }, [confirmAction, t, toast]); const sendReset = useCallback(async (u: UserDto) => { try { await api.post(`/users/${u.id}/send-password-reset`); toast(t("adminUsersResetSent"), "success"); } catch (e: any) { toast(getApiErrorMessage(e, t("adminUsersResetFailed")), "error"); } }, [t, toast]); const remove = useCallback(async (u: UserDto) => { const name = u.userName || u.email || u.id; const message = u.isCurrentUser ? t("adminUsersDeleteSelfWarning") : t("adminUsersDeleteConfirmNamed", { name }); if (!(await confirmAction(message, { title: t("adminUsersDeleteConfirmTitle"), confirmLabel: t("adminUsersDelete"), destructive: true }))) return; try { await api.delete(`/users/${u.id}`); toast(t("adminUsersDeleted"), "info"); await load(); } catch (e) { toast(getApiErrorMessage(e, t("adminUsersDeleteFailed")), "error"); } }, [confirmAction, t, toast]); const columns = useMemo(() => [ { field: "email", headerName: t("profileEmail"), flex: 1.2, minWidth: 220, renderCell: (params) => ( {params.value} {(params.row.raw as UserDto).isCurrentUser ? : null} ), }, { field: "userName", headerName: t("profileUsername"), flex: 1, minWidth: 180 }, { field: "roles", headerName: t("adminUsersRolesLabel"), flex: 1, minWidth: 180, sortable: false, renderCell: (params) => { const roles = params.row.roles as string[]; return ( {roles.length ? roles.map((role) => ) : } ); }, }, { field: "emailConfirmed", headerName: t("adminUsersConfirmed"), width: 130, renderCell: (params) => ( ), }, { field: "actions", headerName: t("adminUsersActions"), minWidth: 300, flex: 1.4, sortable: false, filterable: false, renderCell: (params) => { const user = params.row.raw as UserDto; const isAdmin = (user.roles || []).includes("Admin"); return ( ); }, }, ], [remove, sendReset, setAdminRole, t]); return ( {t("adminUsersTitle")} {t("adminUsersSubtitle")} {t("adminUsersSafetyNotice")} {t("adminUsersCreateUser")} setNewEmail(e.target.value)} fullWidth /> setNewPassword(e.target.value)} fullWidth /> setNewIsAdmin(e.target.checked)} />} label={t("adminUsersAdmin")} /> {isMobile ? ( {!loading && rows.length === 0 ? ( {t("adminUsersNoUsers")} ) : null} {rows.map((row) => { const user = row.raw as UserDto; const isAdmin = user.roles.includes("Admin"); return ( {row.userName || row.email || row.id} {user.isCurrentUser ? : null} {row.email || "—"} {(row.roles as string[]).length ? (row.roles as string[]).map((role) => ( )) : } ); })} ) : ( {!loading && rows.length === 0 ? ( {t("adminUsersNoUsers")} ) : null} )} ); }