2025-12-11 15:06:04 +01:00

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 && user && (
<DeleteUser
user={user}
open={openDeleteUser}
onClose={() => setOpenDeleteUser(false)}
/>
)}
{showConfirmModal && (
<Modal
open={showConfirmModal}
onClose={() => setShowConfirmModal(false)}
title={`Reset Password - ${user.first_name}`}
>
{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>
);
}