First Commit
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
import { api } from "../api";
|
||||
import { useToast } from "../toast";
|
||||
|
||||
type UserDto = {
|
||||
id: string;
|
||||
email?: string | null;
|
||||
userName?: string | null;
|
||||
emailConfirmed: boolean;
|
||||
roles: string[];
|
||||
};
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
const { toast } = useToast();
|
||||
const [users, setUsers] = useState<UserDto[]>([]);
|
||||
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<UserDto[]>("/users");
|
||||
setUsers(res.data ?? []);
|
||||
} catch {
|
||||
setUsers([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const canCreate = useMemo(() => newEmail.trim().length > 3 && newPassword.length >= 6, [newEmail, newPassword]);
|
||||
|
||||
const setAdminRole = async (u: UserDto, isAdmin: boolean) => {
|
||||
try {
|
||||
await api.put(`/users/${u.id}/roles`, { roles: isAdmin ? ["Admin"] : [] });
|
||||
toast("Roles updated.", "success");
|
||||
await load();
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data || e?.message || "Failed to update roles.";
|
||||
toast(String(msg), "error");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReset = async (u: UserDto) => {
|
||||
try {
|
||||
await api.post(`/users/${u.id}/send-password-reset`);
|
||||
toast("Password reset email sent.", "success");
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data || e?.message || "Failed to send reset.";
|
||||
toast(String(msg), "error");
|
||||
}
|
||||
};
|
||||
|
||||
const remove = async (u: UserDto) => {
|
||||
if (!window.confirm(`Delete user ${u.email || u.userName || u.id}?`)) return;
|
||||
try {
|
||||
await api.delete(`/users/${u.id}`);
|
||||
toast("User deleted.", "info");
|
||||
await load();
|
||||
} catch {
|
||||
toast("Failed to delete user.", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 950, mb: 0.5 }}>
|
||||
Users
|
||||
</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 2 }}>Admin-only user management.</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography sx={{ fontWeight: 900, mb: 1 }}>Create user</Typography>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.5 }}>
|
||||
<TextField label="Email" value={newEmail} onChange={(e) => setNewEmail(e.target.value)} />
|
||||
<TextField label="Password" type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, mt: 1.5, flexWrap: "wrap" }}>
|
||||
<FormControlLabel control={<Checkbox checked={newIsAdmin} onChange={(e) => setNewIsAdmin(e.target.checked)} />} label="Admin" />
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!canCreate || loading}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await api.post("/users", { email: newEmail, password: newPassword, roles: newIsAdmin ? ["Admin"] : [] });
|
||||
setNewEmail("");
|
||||
setNewPassword("");
|
||||
setNewIsAdmin(false);
|
||||
toast("User created.", "success");
|
||||
await load();
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data || e?.message || "Failed to create user.";
|
||||
toast(String(msg), "error");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<TableContainer sx={{ borderRadius: 2, border: "1px solid", borderColor: "divider" }}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell>Username</TableCell>
|
||||
<TableCell>Roles</TableCell>
|
||||
<TableCell>Confirmed</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map((u) => {
|
||||
const isAdmin = (u.roles || []).includes("Admin");
|
||||
return (
|
||||
<TableRow key={u.id} hover>
|
||||
<TableCell sx={{ fontWeight: 850 }}>{u.email || ""}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{u.userName || ""}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{u.roles?.length ? u.roles.join(", ") : "-"}</TableCell>
|
||||
<TableCell sx={{ color: "text.secondary" }}>{u.emailConfirmed ? "Yes" : "No"}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 1, flexWrap: "wrap" }}>
|
||||
<Button size="small" variant={isAdmin ? "contained" : "outlined"} onClick={() => void setAdminRole(u, !isAdmin)}>
|
||||
Admin
|
||||
</Button>
|
||||
<Button size="small" variant="outlined" onClick={() => void sendReset(u)}>
|
||||
Send reset
|
||||
</Button>
|
||||
<Button size="small" color="error" variant="outlined" onClick={() => void remove(u)}>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
{!loading && users.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<Typography sx={{ color: "text.secondary", py: 2, textAlign: "center" }}>No users.</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user