206 lines
6.3 KiB
TypeScript
206 lines
6.3 KiB
TypeScript
import { useState } from "react";
|
|
import toast from "react-hot-toast";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
Avatar,
|
|
Typography,
|
|
Chip,
|
|
IconButton,
|
|
Tooltip,
|
|
Stack,
|
|
Box,
|
|
} from "@mui/material";
|
|
import {
|
|
Edit,
|
|
Delete,
|
|
VpnKey,
|
|
InfoOutlined,
|
|
AdminPanelSettings,
|
|
ContentCopy,
|
|
} from "@mui/icons-material";
|
|
import EditUser from "./EditUser/EditUser";
|
|
import { IUser } from "../Pages/Admin/Users/interfaces";
|
|
import DeleteUser from "./DeleteUser/DeleteUser";
|
|
import Modal from "@/app/components/Modal/Modal";
|
|
import { useDispatch } from "react-redux";
|
|
import { AppDispatch } from "@/app/redux/store";
|
|
import { resetPassword } from "@/app/redux/auth/authSlice";
|
|
import Confirm from "../../components/Confirm/Confirm";
|
|
import { ThunkSuccess } from "@/app/redux/types";
|
|
import "./User.scss";
|
|
|
|
interface Props {
|
|
user: IUser;
|
|
}
|
|
|
|
export default function UserRoleCard({ user }: Props) {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
|
const [openDeleteUser, setOpenDeleteUser] = useState(false);
|
|
const dispatch = useDispatch<AppDispatch>();
|
|
const { username, first_name, last_name, email, groups } = user;
|
|
const [newPassword, setNewPassword] = useState<string | null>(null);
|
|
const handleEditClick = () => {
|
|
setIsEditing(!isEditing);
|
|
};
|
|
|
|
const handleDeleteClick = () => {
|
|
setOpenDeleteUser(true);
|
|
};
|
|
|
|
const handleResetPasswordSubmit = async () => {
|
|
try {
|
|
const resultAction = await dispatch(
|
|
resetPassword({ id: user.id as string })
|
|
);
|
|
if (resetPassword.fulfilled.match(resultAction)) {
|
|
setNewPassword(
|
|
(
|
|
resultAction.payload as ThunkSuccess<{
|
|
success: boolean;
|
|
message: string;
|
|
newPassword: string | null;
|
|
}>
|
|
)?.newPassword || null
|
|
);
|
|
toast.success(
|
|
(
|
|
resultAction.payload as ThunkSuccess<{
|
|
success: boolean;
|
|
message: string;
|
|
newPassword: string | null;
|
|
}>
|
|
)?.message || "Password reset successfully"
|
|
);
|
|
} else if (resetPassword.rejected.match(resultAction)) {
|
|
toast.error(
|
|
(resultAction.payload as string) || "Failed to reset password"
|
|
);
|
|
}
|
|
} catch (e: unknown) {
|
|
const message = e instanceof Error ? e.message : "Unexpected error";
|
|
toast.error(message);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card sx={{ mb: 2, minWidth: "100%" }}>
|
|
<CardContent>
|
|
{/* Header */}
|
|
<Stack direction="row" alignItems="center" spacing={2}>
|
|
<Avatar>{username?.slice(0, 2).toUpperCase()}</Avatar>
|
|
<Box flexGrow={1}>
|
|
<Typography fontWeight="bold">{username}</Typography>
|
|
<Typography variant="body2">
|
|
{first_name} {last_name}
|
|
</Typography>
|
|
<Typography variant="caption">{email}</Typography>
|
|
</Box>
|
|
{true && (
|
|
<Chip icon={<AdminPanelSettings />} label="Admin" size="small" />
|
|
)}
|
|
<Tooltip title="Edit">
|
|
<IconButton onClick={handleEditClick}>
|
|
<Edit />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip
|
|
title="Reset Password"
|
|
onClick={() => {
|
|
setNewPassword(null);
|
|
setShowConfirmModal(true);
|
|
}}
|
|
>
|
|
<IconButton>
|
|
<VpnKey />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Delete">
|
|
<IconButton onClick={handleDeleteClick}>
|
|
<Delete />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Stack>
|
|
|
|
{/* Merchants + Roles */}
|
|
<Box mt={2}>
|
|
<Typography fontWeight="bold">Merchants</Typography>
|
|
</Box>
|
|
|
|
<Box mt={2}>
|
|
<Typography fontWeight="bold">
|
|
Roles
|
|
<Tooltip title="Roles assigned to this user">
|
|
<InfoOutlined fontSize="small" />
|
|
</Tooltip>
|
|
</Typography>
|
|
<Stack direction="row" spacing={1} mt={1} flexWrap="wrap">
|
|
<Stack direction="row" spacing={1}>
|
|
{groups.map(role => (
|
|
<Chip key={role} label={role} size="small" />
|
|
))}
|
|
</Stack>
|
|
</Stack>
|
|
</Box>
|
|
<div
|
|
className={`user-card__edit-transition${
|
|
isEditing ? " user-card__edit-transition--open" : ""
|
|
}`}
|
|
>
|
|
{isEditing && <EditUser user={user} />}
|
|
</div>
|
|
{openDeleteUser && (
|
|
<DeleteUser
|
|
user={user}
|
|
open={openDeleteUser}
|
|
onClose={() => setOpenDeleteUser(false)}
|
|
/>
|
|
)}
|
|
{showConfirmModal && (
|
|
<Modal
|
|
open={showConfirmModal}
|
|
onClose={() => setShowConfirmModal(false)}
|
|
title="Reset Password"
|
|
>
|
|
{newPassword && (
|
|
<div className="reset-password__content">
|
|
<Stack direction="row" alignItems="center" spacing={1}>
|
|
<Typography variant="body1">
|
|
<code>{newPassword}</code>
|
|
</Typography>
|
|
<Tooltip title="Copy to clipboard">
|
|
<IconButton
|
|
aria-label="Copy temporary password"
|
|
onClick={async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(newPassword);
|
|
toast.success("Copied to clipboard");
|
|
} catch {
|
|
toast.error("Failed to copy");
|
|
}
|
|
}}
|
|
size="small"
|
|
>
|
|
<ContentCopy fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Stack>
|
|
</div>
|
|
)}
|
|
{!newPassword && (
|
|
<Confirm
|
|
onClose={() => setShowConfirmModal(false)}
|
|
onSubmit={handleResetPasswordSubmit}
|
|
message="Are you sure you want to reset the password for this user?"
|
|
confirmLabel="Reset Password"
|
|
cancelLabel="Cancel"
|
|
/>
|
|
)}
|
|
</Modal>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|