Skip to content

Commit

Permalink
Add "Reset Password" Functionality on Learner Page
Browse files Browse the repository at this point in the history
  • Loading branch information
chaitanyakole committed Feb 24, 2025
1 parent f3dff8e commit af0b481
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 27 deletions.
11 changes: 10 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@
"SEND_INVITATION": "Send Invitation",
"METABASE_REPORTS": "Metabase Report",
"USER_JOURNEY_REPORT": "User Journey Report",
"USER_RESPONSE_EVENT": "User Response Event Report"
"USER_RESPONSE_EVENT": "User Response Event Report",
"RESET_PASSWORD": "Reset Password",
"NEW_PASSWORD": "New Password",
"CONFIRM_PASSWORD": "Confirm Password",
"PASSWORD_RESET_SUCCESSFUL": "Password Reset Successful!",
"PASSWORD_RESET_FAILED": "Password Reset Failed. Please try again."
},
"LOGIN_PAGE": {
"USERNAME": "Username",
Expand Down Expand Up @@ -504,5 +509,9 @@
"INVITATION_DELETED_SUCCESS": "Invitation deleted successfully",
"INVITATION_DELETED_ERROR": "Failed to delete invitation",
"FETCH_INVITATIONS_ERROR": "Failed to fetch invitations"
},
"AUTH": {
"RESET_PASSWORD": "Reset Password",
"RESET_PASSWORD_INSTRUCTIONS": ""
}
}
59 changes: 41 additions & 18 deletions src/components/ActionIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { useTranslation } from "next-i18next";
import { Box, Typography, Tooltip, Button } from "@mui/material";
import { useRouter } from "next/router";
Expand All @@ -7,6 +7,8 @@ import editIcon from "../../public/images/editIcon.svg";
import cohortIcon from "../../public/images/apartment.svg";
import addIcon from "../../public/images/addIcon.svg";
import MetabaseReportsMenu from "./MetabaseReportMenu";
import { ResetPasswordModal } from "./ResetPassword";

interface ActionCellProps {
onEdit: (rowData: any) => void;
onDelete: (rowData: any) => void;
Expand All @@ -21,6 +23,7 @@ interface ActionCellProps {
onAdd: (rowData: any) => void;
showReports?: boolean;
showLearnerReports?: boolean;
showResetPassword?: boolean;
}

const ActionIcon: React.FC<ActionCellProps> = ({
Expand All @@ -36,10 +39,12 @@ const ActionIcon: React.FC<ActionCellProps> = ({
reassignType,
showReports,
showLearnerReports = false,
showResetPassword = false,
}) => {
const { t } = useTranslation();
const router = useRouter();

const [isResetModalOpen, setIsResetModalOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState<any>(null);
const isCohortAdmin = rowData?.userRoleTenantMapping?.code === "cohort_admin";
const currentPage = router.pathname;

Expand All @@ -64,6 +69,20 @@ const ActionIcon: React.FC<ActionCellProps> = ({
visible: showLearnerReports,
enabled: true,
},
resetPassword: {
visible: showResetPassword,
enabled: true,
},
};

const handleResetPassword = (userData: any) => {
setSelectedUser(userData);
setIsResetModalOpen(true);
};

const handleCloseResetModal = () => {
setIsResetModalOpen(false);
setSelectedUser(null);
};

const commonButtonStyles = (enabled: boolean) => ({
Expand Down Expand Up @@ -116,21 +135,19 @@ const ActionIcon: React.FC<ActionCellProps> = ({
if (!buttonStates.learnerReports.visible) return null;

return (
<>
<Tooltip title={t("COMMON.EDIT")}>
<Box
onClick={() => buttonStates.editDelete.enabled && onEdit(rowData)}
sx={{
...commonButtonStyles(buttonStates.editDelete.enabled),
backgroundColor: buttonStates.editDelete.enabled
? "#E3EAF0"
: "#d3d3d3",
}}
>
<Image src={editIcon} alt="" />
</Box>
</Tooltip>
</>
<Tooltip title={t("COMMON.EDIT")}>
<Box
onClick={() => buttonStates.editDelete.enabled && onEdit(rowData)}
sx={{
...commonButtonStyles(buttonStates.editDelete.enabled),
backgroundColor: buttonStates.editDelete.enabled
? "#E3EAF0"
: "#d3d3d3",
}}
>
<Image src={editIcon} alt="" />
</Box>
</Tooltip>
);
};

Expand Down Expand Up @@ -167,15 +184,21 @@ const ActionIcon: React.FC<ActionCellProps> = ({
>
{renderAddButton()}
{renderEditDeleteButtons()}

{renderReassignButton()}

<MetabaseReportsMenu
buttonStates={buttonStates}
onEdit={onEdit}
onDelete={onDelete}
onResetPassword={handleResetPassword}
rowData={rowData}
/>

<ResetPasswordModal
open={isResetModalOpen}
onClose={handleCloseResetModal}
userData={selectedUser}
/>
</Box>
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/KaTableComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ interface KaTableComponentProps {
allowEditIcon?: boolean;
showReports?: boolean;
showLearnerReports?: boolean;
showResetPassword?: boolean;
showForghandleForgotPasswordClickotPassword?: void;
}

const KaTableComponent: React.FC<KaTableComponentProps> = ({
Expand All @@ -77,6 +79,7 @@ const KaTableComponent: React.FC<KaTableComponentProps> = ({
allowEditIcon,
showReports,
showLearnerReports,
showResetPassword,
}) => {
const [selectedRowIds, setSelectedRowIds] = useState<number[]>([]);
const { t } = useTranslation();
Expand Down Expand Up @@ -152,6 +155,7 @@ const KaTableComponent: React.FC<KaTableComponentProps> = ({
onDelete={onDelete}
allowEditIcon={allowEditIcon}
showReports={showReports}
showResetPassword={showResetPassword}
showLearnerReports={showLearnerReports}
// userAction={props.rowData?.userId}
disable={props.rowData?.status === Status.ARCHIVED}
Expand Down
25 changes: 23 additions & 2 deletions src/components/MetabaseReportMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,37 @@ import TimelineIcon from "@mui/icons-material/Timeline";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import LockResetIcon from "@mui/icons-material/LockReset";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";

interface ButtonState {
visible: boolean | undefined;
enabled: boolean | undefined;
}

interface ButtonStates {
add: ButtonState;
editDelete: ButtonState;
reassign: ButtonState;
reports: ButtonState;
learnerReports: ButtonState;
resetPassword: ButtonState;
}

interface MetabaseDashboardProps {
onEdit: (rowData: any) => void;
onDelete: (rowData: any) => void;
onResetPassword?: (rowData: any) => void;
rowData: any;
buttonStates: ButtonStates;
}
// Interface for individual button state

const MetabaseReportsMenu: React.FC<MetabaseDashboardProps> = ({
buttonStates,
onEdit,
onDelete,
onResetPassword,
rowData,
}) => {
const { t } = useTranslation();
Expand All @@ -46,13 +51,15 @@ const MetabaseReportsMenu: React.FC<MetabaseDashboardProps> = ({
const handleClose = () => {
setAnchorEl(null);
};

const getUserRowData = (dashboardType: string) => {
return rowData?.tenantId && !rowData?.userId
? { tenantId: rowData.tenantId, dashboardType }
: rowData?.userId
? { userId: rowData.userId, dashboardType }
: {};
};

const handleNavigation = (type: string) => {
const userRowData: object = getUserRowData(type);
router.push({
Expand All @@ -76,6 +83,13 @@ const MetabaseReportsMenu: React.FC<MetabaseDashboardProps> = ({
}
};

const handleResetPassword = () => {
if (buttonStates.resetPassword?.enabled && onResetPassword) {
onResetPassword(rowData);
handleClose();
}
};

const reportMenuItems = [
{
visible: buttonStates.reports.visible,
Expand Down Expand Up @@ -109,6 +123,13 @@ const MetabaseReportsMenu: React.FC<MetabaseDashboardProps> = ({
onClick: handleEdit,
disabled: !buttonStates.editDelete.enabled,
},
{
visible: buttonStates.resetPassword?.visible,
title: t("COMMON.RESET_PASSWORD"),
icon: <LockResetIcon />,
onClick: handleResetPassword,
disabled: !buttonStates.resetPassword?.enabled,
},
{
visible: buttonStates.editDelete.visible,
title: t("COMMON.DELETE"),
Expand Down Expand Up @@ -168,7 +189,7 @@ const MetabaseReportsMenu: React.FC<MetabaseDashboardProps> = ({

{/* Add divider if both report and action items are visible */}
{reportMenuItems.some((item) => item.visible) &&
actionMenuItems.some((item) => item.visible)}
actionMenuItems.some((item) => item.visible) && <Divider />}

{actionMenuItems.map(
(item, index) =>
Expand Down
130 changes: 130 additions & 0 deletions src/components/ResetPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useState } from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
Box,
Typography,
CircularProgress,
} from "@mui/material";
import { useTranslation } from "next-i18next";
import { resetPassword } from "@/services/LoginService";
import { showToastMessage } from "./Toastify";

interface ResetPasswordModalProps {
open: boolean;
onClose: () => void;
userData: any;
}

export const ResetPasswordModal: React.FC<ResetPasswordModalProps> = ({
open,
onClose,
userData,
}) => {
const { t } = useTranslation();
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(false);

const resetForm = () => {
setNewPassword("");
setConfirmPassword("");
setError("");
};

const handleClose = () => {
resetForm();
onClose();
};

const validatePasswords = () => {
if (newPassword.length < 8) {
setError("Password must be at least 8 characters long");
return false;
}
if (newPassword !== confirmPassword) {
setError("Passwords do not match");
return false;
}
return true;
};

const handleResetConfirm = async () => {
if (!validatePasswords()) return;

setIsLoading(true);
setError("");
const tenantid = userData?.tenantId;
const userObj = {
userName: userData?.username,
newPassword: newPassword,
};

try {
const response = await resetPassword(userObj, tenantid);
if (response?.responseCode !== 200) {
throw new Error("Failed to reset password");
}
showToastMessage(t("COMMON.PASSWORD_RESET_SUCCESSFUL"), "success");
handleClose();
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
showToastMessage(t("COMMON.PASSWORD_RESET_FAILED"), "error");
} finally {
setIsLoading(false);
}
};

return (
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle>{t("COMMON.RESET_PASSWORD")}</DialogTitle>
<DialogContent>
<Box sx={{ mt: 2 }}>
<Typography variant="body1" sx={{ mb: 2 }}>
Reset password for user: <strong>{userData?.name}</strong>
</Typography>

<TextField
fullWidth
type="password"
label={t("COMMON.NEW_PASSWORD")}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
margin="normal"
error={!!error}
/>

<TextField
fullWidth
type="password"
label={t("COMMON.CONFIRM_PASSWORD")}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
margin="normal"
error={!!error}
helperText={error}
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 2 }}>
<Button onClick={handleClose} disabled={isLoading}>
{t("COMMON.CANCEL")}
</Button>
<Button
onClick={handleResetConfirm}
variant="contained"
sx={{ color: "white" }}
disabled={isLoading || !newPassword || !confirmPassword}
startIcon={isLoading ? <CircularProgress size={20} /> : null}
>
{t("COMMON.RESET_PASSWORD")}
</Button>
</DialogActions>
</Dialog>
);
};
1 change: 1 addition & 0 deletions src/components/UserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ const UserTable: React.FC<UserTableProps> = ({
allowEditIcon={true}
showReports={true}
showLearnerReports={true}
showResetPassword={true}
noDataMessage={data?.length === 0 ? t("COMMON.NO_USER_FOUND") : ""}
/>
</Box>
Expand Down
Loading

0 comments on commit af0b481

Please sign in to comment.