feat: admin staff management
This commit is contained in:
parent
60d187b300
commit
3061b88343
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
const useQuery = () => {
|
||||||
|
const { search } = useLocation();
|
||||||
|
|
||||||
|
return useMemo(() => new URLSearchParams(search), [search]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useQuery;
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useScript = ({
|
||||||
|
src,
|
||||||
|
async = true,
|
||||||
|
type = 'text/javascript',
|
||||||
|
noModule = false,
|
||||||
|
}) => {
|
||||||
|
const [status, setStatus] = useState(src ? 'loading' : 'idle');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!src) {
|
||||||
|
setStatus('idle');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the script is already present
|
||||||
|
let script = document.querySelector(`script[src="${src}"]`);
|
||||||
|
|
||||||
|
if (!script) {
|
||||||
|
// Create script element and add it to the document head
|
||||||
|
script = document.createElement('script');
|
||||||
|
script.src = src;
|
||||||
|
script.async = async;
|
||||||
|
script.type = type;
|
||||||
|
script.noModule = noModule;
|
||||||
|
|
||||||
|
// Set initial status
|
||||||
|
setStatus('loading');
|
||||||
|
|
||||||
|
// Script event listeners
|
||||||
|
const onScriptLoad = () => setStatus('ready');
|
||||||
|
const onScriptError = () => setStatus('error');
|
||||||
|
|
||||||
|
script.addEventListener('load', onScriptLoad);
|
||||||
|
script.addEventListener('error', onScriptError);
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
return () => {
|
||||||
|
script.removeEventListener('load', onScriptLoad);
|
||||||
|
script.removeEventListener('error', onScriptError);
|
||||||
|
document.head.removeChild(script);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// If script is already present, update status accordingly
|
||||||
|
setStatus('ready');
|
||||||
|
}
|
||||||
|
}, [src, async, type, noModule]);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useScript;
|
||||||
|
|
@ -10,10 +10,11 @@ import MockPayment from "../views/MockPayment";
|
||||||
import Users from "../views/User";
|
import Users from "../views/User";
|
||||||
import ClinicSetup from "../views/ClinicSetup";
|
import ClinicSetup from "../views/ClinicSetup";
|
||||||
import ClinicTranscripts from "../views/ClinicTranscripts";
|
import ClinicTranscripts from "../views/ClinicTranscripts";
|
||||||
import ContractManagement from "../views/ContractManagement";
|
|
||||||
import MasterDataManagement from "../views/MasterData";
|
import MasterDataManagement from "../views/MasterData";
|
||||||
import PaymentManagement from "../views/PaymentManagement";
|
import PaymentManagement from "../views/PaymentManagement";
|
||||||
import ClinicDocUpdater from "../views/ClinicDocUpdate";
|
import ClinicDocUpdater from "../views/ClinicDocUpdate";
|
||||||
|
import ForgotPassword from "../views/ForgotPassword";
|
||||||
|
import ResetPassword from "../views/ResetPassword";
|
||||||
|
|
||||||
export const routesData = [
|
export const routesData = [
|
||||||
{
|
{
|
||||||
|
|
@ -43,6 +44,14 @@ export const routesData = [
|
||||||
path: "signup/your-details",
|
path: "signup/your-details",
|
||||||
component: YourDetailsForm,
|
component: YourDetailsForm,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "forgotpassword",
|
||||||
|
component: ForgotPassword,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'reset-password',
|
||||||
|
component: ResetPassword,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { axiosInstance } from "../config/api";
|
import { axiosInstance } from "../config/api";
|
||||||
|
|
||||||
export const signup = (data) => {
|
export const signup = (data) => {
|
||||||
const url = '/auth/register';
|
const url = "/auth/register";
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axiosInstance
|
axiosInstance
|
||||||
.post(url, data)
|
.post(url, data)
|
||||||
|
|
@ -10,9 +10,34 @@ export const signup = (data) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createAdmin = (data) => {
|
||||||
|
const url = "/admin/user";
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axiosInstance
|
||||||
|
.post(url, data)
|
||||||
|
.then((response) => resolve(response))
|
||||||
|
.catch((err) => reject(err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAdmins = (params) => {
|
||||||
|
|
||||||
|
let searchParams = new URLSearchParams();
|
||||||
|
searchParams.append("size", params?.pagination?.pageSize ?? 10);
|
||||||
|
searchParams.append("page", params?.pagination.pageIndex ?? 0);
|
||||||
|
searchParams.append("search", params?.globalFilter ?? "");
|
||||||
|
|
||||||
|
const url = `/admin/?${searchParams.toString()}`;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axiosInstance
|
||||||
|
.get(url)
|
||||||
|
.then((response) => resolve(response))
|
||||||
|
.catch((err) => reject(err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const getEmailOtp = (data) => {
|
export const getEmailOtp = (data) => {
|
||||||
const url = '/auth/send-otp';
|
const url = "/auth/send-otp";
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axiosInstance
|
axiosInstance
|
||||||
.post(url, null, { params: data })
|
.post(url, null, { params: data })
|
||||||
|
|
@ -22,17 +47,37 @@ export const getEmailOtp = (data) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const verifyOtp = (data) => {
|
export const verifyOtp = (data) => {
|
||||||
const url = '/auth/verify-otp';
|
const url = "/auth/verify-otp";
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axiosInstance
|
axiosInstance
|
||||||
.post(url, data)
|
.post(url, data)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.status == 200) {
|
if (err.status == 200) {
|
||||||
resolve(err)
|
resolve(err);
|
||||||
} else {
|
} else {
|
||||||
reject(err)
|
reject(err);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => resolve(response))
|
.then((response) => resolve(response));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const forgotPassword = (data) => {
|
||||||
|
const url = "/auth/admin/forget-password";
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axiosInstance
|
||||||
|
.post(url, null, { params: data })
|
||||||
|
.then((response) => resolve(response))
|
||||||
|
.catch((err) => reject(err));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = (data) => {
|
||||||
|
const url = "/auth/admin/reset-password";
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axiosInstance
|
||||||
|
.post(url, data)
|
||||||
|
.then((response) => resolve(response))
|
||||||
|
.catch((err) => reject(err));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { Box, InputLabel, TextField, Typography } from '@mui/material';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { NOTIFICATION } from '../../constants';
|
||||||
|
import { forgotPassword } from '../../services/auth.services';
|
||||||
|
import { pushNotification } from '../../utils/notification';
|
||||||
|
import { useStyles } from './forgotPasswordStyles';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
email: Yup.string()
|
||||||
|
.email('Invalid Email Address')
|
||||||
|
.required('Email is required'),
|
||||||
|
});
|
||||||
|
|
||||||
|
function ForgotPasswordForm({ onSubmit, setEmail }) {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const emailRef = useRef(null);
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: { email: '' },
|
||||||
|
validationSchema,
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
try {
|
||||||
|
const response = await forgotPassword(values);
|
||||||
|
if (response.status === 200) {
|
||||||
|
// setTimer(15);
|
||||||
|
setEmail(values.email);
|
||||||
|
// setShowButton(false);
|
||||||
|
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
formik.setErrors({
|
||||||
|
email: error?.response?.data?.message,
|
||||||
|
});
|
||||||
|
emailRef.current.focus();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
formik.setSubmitting(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmitClick = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formikError = await formik.validateForm();
|
||||||
|
const errors = Object.keys(formikError);
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
// Find the first invalid field and focus it
|
||||||
|
const firstErrorField = errors[0];
|
||||||
|
const firstErrorRef = emailRef?.current;
|
||||||
|
|
||||||
|
if (firstErrorRef) {
|
||||||
|
// Scroll to the first invalid field smoothly
|
||||||
|
if (typeof firstErrorRef?.scrollIntoView === 'function') {
|
||||||
|
firstErrorRef?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the field after a slight delay (to ensure scrolling completes first)
|
||||||
|
setTimeout(() => firstErrorRef.focus(), 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error notification
|
||||||
|
if (formik?.touched[firstErrorField])
|
||||||
|
pushNotification(formikError[firstErrorField], NOTIFICATION.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={classes.right}>
|
||||||
|
<form onSubmit={handleSubmitClick} className={classes.form}>
|
||||||
|
<Box className={classes.formBody}>
|
||||||
|
<Box className={classes.formHeader}>
|
||||||
|
<Typography className={classes.subHeading}>
|
||||||
|
Please provide the email address that you used when you signed up
|
||||||
|
for your account.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<InputLabel
|
||||||
|
shrink={false}
|
||||||
|
htmlFor={'email'}
|
||||||
|
className={classes.labelStyle}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
className={classes.inputTitle}
|
||||||
|
style={{
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Email Address*
|
||||||
|
</Typography>
|
||||||
|
</InputLabel>
|
||||||
|
<TextField
|
||||||
|
style={{ marginTop: '2px' }}
|
||||||
|
color="secondary"
|
||||||
|
placeholder="Enter Email ID"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
name="email"
|
||||||
|
value={formik.values.email}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
inputRef={emailRef}
|
||||||
|
error={Boolean(formik.errors.email && formik.touched.email)}
|
||||||
|
helperText={
|
||||||
|
formik.errors.email && formik.touched.email
|
||||||
|
? formik.errors.email
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{/* )} */}
|
||||||
|
<Typography className={classes.sendEmailText}>
|
||||||
|
We will send you an email that will allow you to reset your password.
|
||||||
|
</Typography>
|
||||||
|
<LoadingButton
|
||||||
|
size="large"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
className={classes.submitButton}
|
||||||
|
loading={formik.isSubmitting}
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</LoadingButton>
|
||||||
|
{/* <Box className={classes.formFooter}>
|
||||||
|
<Typography className={classes.emailText}>
|
||||||
|
If you no longer have access to this email account, please contact
|
||||||
|
our support team at
|
||||||
|
</Typography>
|
||||||
|
<Typography className={classes.emailText}>
|
||||||
|
<a href={`mailto:${CONTACT_EMAIL}`}>{CONTACT_EMAIL}</a>
|
||||||
|
</Typography>
|
||||||
|
</Box> */}
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPasswordForm;
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import backgroundImage from '../../assets/images/background/background.jpg';
|
||||||
|
import { pxToRem } from '../../theme/typography';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
height: '100vh',
|
||||||
|
backgroundImage: `url(${backgroundImage})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
width: '370px',
|
||||||
|
maxHeight: '835px',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
width: '500px',
|
||||||
|
maxHeight: '735px',
|
||||||
|
},
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderRadius: theme.spacing(3.8),
|
||||||
|
opacity: '0.95',
|
||||||
|
},
|
||||||
|
forgotPasswordHeader: {
|
||||||
|
marginTop: theme.spacing(4.5),
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
textAlign: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: '65%',
|
||||||
|
},
|
||||||
|
formBody: {
|
||||||
|
justifyContent: 'start',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
// height: '162.5px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
// height: '234px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
// height: '325px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& .MuiTextField-root': {
|
||||||
|
marginTop: theme.spacing(0.8),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
marginTop: theme.spacing(0.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
marginTop: theme.spacing(0.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
marginTop: theme.spacing(4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formHeader: {
|
||||||
|
marginTop: theme.spacing(1.2),
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
formFooter: {
|
||||||
|
marginBottom: theme.spacing(4.0),
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
fontFamily: 'Inter-Bold',
|
||||||
|
// width: '70%',
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subHeading: {
|
||||||
|
lineHeight: 1.1,
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
marginBottom: '0px',
|
||||||
|
fontSize: pxToRem(3.2),
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
// paddingBottom: '20px',
|
||||||
|
},
|
||||||
|
inputTitle: {
|
||||||
|
'&.MuiTypography-body1': {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
},
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
textAlign: 'left',
|
||||||
|
// opacity: '0.54',
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
marginBottom: theme.spacing(0.6),
|
||||||
|
marginLeft: theme.spacing(0.6),
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
paddingLeft: '30px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '50px',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
width: '50px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
fontFamily: 'Inter-Regular',
|
||||||
|
fontWeight: 500,
|
||||||
|
marginTop: theme.spacing(1.0),
|
||||||
|
marginBottom: theme.spacing(3.0),
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '20px',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
padding: '20px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
padding: '20px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
height: '10px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sendEmailText: {
|
||||||
|
marginTop: theme.spacing(2.0),
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: pxToRem(10.4),
|
||||||
|
},
|
||||||
|
emailText: {
|
||||||
|
lineHeight: 1.1,
|
||||||
|
fontSize: pxToRem(12.48),
|
||||||
|
},
|
||||||
|
emailSentMainDiv: {
|
||||||
|
width: '70%',
|
||||||
|
paddingTop: theme.spacing(3.4),
|
||||||
|
paddingBottom: theme.spacing(7.0),
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ArrowBackOutlined } from '@mui/icons-material';
|
||||||
|
import { Box, Card, Grid, Typography } from '@mui/material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import ForgotPasswordForm from './ForgotPasswordForm';
|
||||||
|
import { useStyles } from './forgotPasswordStyles';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
function ForgotPassword() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [resetPasswordLinkSend, setResetPasswordLinkSend] = useState(false);
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
|
||||||
|
const handleFormSubmit = () => {
|
||||||
|
setResetPasswordLinkSend(true);
|
||||||
|
};
|
||||||
|
const handleGoBackClick = () => {
|
||||||
|
navigate('/auth/login');
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box className={classes.root}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
<Grid container display="flex" justifyContent="center">
|
||||||
|
{!resetPasswordLinkSend ? (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
className={classes.forgotPasswordHeader}
|
||||||
|
>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<ArrowBackOutlined
|
||||||
|
className={classes.icon}
|
||||||
|
onClick={handleGoBackClick}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={10}>
|
||||||
|
<Typography variant="Inter-bold" className={classes.heading}>
|
||||||
|
Reset your Password
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<ForgotPasswordForm
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
setEmail={setEmail}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Box className={classes.emailSentMainDiv}>
|
||||||
|
{/* <Box width="90%"> */}
|
||||||
|
<Typography
|
||||||
|
variant="Gilroy-SemiBold"
|
||||||
|
className={classes.heading}
|
||||||
|
marginBottom="20px"
|
||||||
|
>
|
||||||
|
Email Sent
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="Gilroy-normal"
|
||||||
|
className={classes.subheading}
|
||||||
|
// marginBottom="30px"
|
||||||
|
>
|
||||||
|
{/* {` An email with instructions on how to reset your password has
|
||||||
|
been sent to ${email}. It is valid for next 24 hrs.
|
||||||
|
Check your spam or junk folder if you don’t see the email in
|
||||||
|
your inbox.`} */}
|
||||||
|
{`An email with instructions on how to reset your password has
|
||||||
|
been sent to `}
|
||||||
|
<span style={{ fontWeight: 'bold' }}>{email}</span>
|
||||||
|
{`. It is valid for the next 24 hrs. Check your spam or junk folder if you don’t see the email in
|
||||||
|
your inbox.`}
|
||||||
|
</Typography>
|
||||||
|
{/* </Box> */}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotPassword;
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useStyles } from './resetPasswordStyles';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import PasswordValidation from '../Login/component/PasswordValidation';
|
||||||
|
import { VisibilityOffOutlined, VisibilityOutlined } from '@mui/icons-material';
|
||||||
|
import useQuery from '../../hooks/useQuery';
|
||||||
|
import {
|
||||||
|
passwordLengthRegex,
|
||||||
|
passwordLetterRegex,
|
||||||
|
passwordNumberRegex,
|
||||||
|
passwordSpacesRegex,
|
||||||
|
} from '../../utils/regex';
|
||||||
|
import { pushNotification } from '../../utils/notification';
|
||||||
|
import { NOTIFICATION } from '../../constants';
|
||||||
|
import { resetPassword } from '../../services/auth.services';
|
||||||
|
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
password: Yup.string()
|
||||||
|
.matches(passwordLengthRegex, 'Password must be at least 8 characters')
|
||||||
|
.matches(passwordLetterRegex, 'Password must contain at least one letter')
|
||||||
|
.matches(passwordNumberRegex, 'Password must contain at least one number')
|
||||||
|
.matches(passwordSpacesRegex, 'Password must not start or end with a space')
|
||||||
|
.required('Password is required'),
|
||||||
|
confirmPassword: Yup.string()
|
||||||
|
.oneOf([Yup.ref('password'), null], 'Passwords must match')
|
||||||
|
.required('Passwords must match'),
|
||||||
|
});
|
||||||
|
|
||||||
|
function ResetPasswordForm({ onSubmit }) {
|
||||||
|
const query = useQuery();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
|
|
||||||
|
const fieldRefs = {
|
||||||
|
password: useRef(null),
|
||||||
|
confirmPassword: useRef(null),
|
||||||
|
};
|
||||||
|
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: { password: '', confirmPassword: '' },
|
||||||
|
validationSchema,
|
||||||
|
onSubmit: (values) => handleSubmit(values, formik),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async (values, formik) => {
|
||||||
|
try {
|
||||||
|
values.token = query.get('token');
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
token: values.token,
|
||||||
|
password: values.password,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await resetPassword(requestBody);
|
||||||
|
if (response.status === 200) {
|
||||||
|
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
|
||||||
|
}
|
||||||
|
onSubmit();
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
formik.setSubmitting(false);
|
||||||
|
formik.resetForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleTogglePasswordVisibility = (field) => {
|
||||||
|
if (field === 'password') {
|
||||||
|
setShowPassword((prevShowPassword) => !prevShowPassword);
|
||||||
|
} else if (field === 'confirmPassword') {
|
||||||
|
setShowConfirmPassword(
|
||||||
|
(prevShowConfirmPassword) => !prevShowConfirmPassword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitClick = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formikErrors = await formik.validateForm();
|
||||||
|
const errors = Object.keys(formikErrors);
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
// Find the first invalid field and focus it
|
||||||
|
const firstErrorField = errors[0];
|
||||||
|
const firstErrorRef = fieldRefs[firstErrorField]?.current;
|
||||||
|
|
||||||
|
if (firstErrorRef) {
|
||||||
|
// Scroll to the first invalid field smoothly
|
||||||
|
if (typeof firstErrorRef?.scrollIntoView === 'function') {
|
||||||
|
firstErrorRef?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the field after a slight delay (to ensure scrolling completes first)
|
||||||
|
setTimeout(() => firstErrorRef.focus(), 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error notification
|
||||||
|
if (formik?.touched[firstErrorField])
|
||||||
|
pushNotification(formikErrors[firstErrorField], NOTIFICATION.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={classes.right}>
|
||||||
|
<form onSubmit={handleSubmitClick} className={classes.form}>
|
||||||
|
<Box className={classes.formBody}>
|
||||||
|
<Box className={classes.formHeader}>
|
||||||
|
<Typography className={classes.subHeading}>
|
||||||
|
Note: Password must be at least 8 characters, contain 1 letter and
|
||||||
|
1 number, with no spaces at the beginning/end.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<InputLabel
|
||||||
|
shrink={false}
|
||||||
|
htmlFor={'password'}
|
||||||
|
className={classes.labelStyle}
|
||||||
|
>
|
||||||
|
<Typography className={classes.inputTitle}>
|
||||||
|
Enter New Password
|
||||||
|
</Typography>
|
||||||
|
</InputLabel>
|
||||||
|
<TextField
|
||||||
|
color="secondary"
|
||||||
|
placeholder="Enter Password"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
name="password"
|
||||||
|
value={formik.values.password}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
inputRef={fieldRefs.password}
|
||||||
|
error={Boolean(formik.errors.password && formik.touched.password)}
|
||||||
|
helperText={
|
||||||
|
formik.errors.password && formik.touched.password
|
||||||
|
? formik.errors.password
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => handleTogglePasswordVisibility('password')}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{!showPassword ? (
|
||||||
|
<VisibilityOffOutlined />
|
||||||
|
) : (
|
||||||
|
<VisibilityOutlined />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Password validation display */}
|
||||||
|
{!formik.isValid && (
|
||||||
|
<>
|
||||||
|
<Typography className={classes.passwordCheckListTitle}>
|
||||||
|
Note: Password must be
|
||||||
|
</Typography>
|
||||||
|
<Box className={classes.passwordCheckList}>
|
||||||
|
<PasswordValidation
|
||||||
|
isValid={passwordLengthRegex.test(formik.values.password)}
|
||||||
|
text="at least 6 characters"
|
||||||
|
/>
|
||||||
|
<PasswordValidation
|
||||||
|
isValid={passwordLetterRegex.test(formik.values.password)}
|
||||||
|
text="at least 1 letter"
|
||||||
|
/>
|
||||||
|
<PasswordValidation
|
||||||
|
isValid={passwordNumberRegex.test(formik.values.password)}
|
||||||
|
text="at least 1 number"
|
||||||
|
/>
|
||||||
|
<PasswordValidation
|
||||||
|
isValid={passwordSpacesRegex.test(formik.values.password)}
|
||||||
|
text="with no spaces at the beginning/end"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className={classes.confirmResetBox}>
|
||||||
|
<InputLabel
|
||||||
|
shrink={false}
|
||||||
|
htmlFor={'confirmPassword'}
|
||||||
|
className={classes.labelStyle}
|
||||||
|
>
|
||||||
|
<Typography className={classes.inputTitle}>
|
||||||
|
Confirm New Password
|
||||||
|
</Typography>
|
||||||
|
</InputLabel>
|
||||||
|
<TextField
|
||||||
|
className={classes.resetPasswordTextField}
|
||||||
|
color="secondary"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
|
name="confirmPassword"
|
||||||
|
value={formik.values.confirmPassword}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={formik.handleBlur}
|
||||||
|
inputRef={fieldRefs.confirmPassword}
|
||||||
|
error={Boolean(
|
||||||
|
formik.errors.confirmPassword && formik.touched.confirmPassword
|
||||||
|
)}
|
||||||
|
helperText={
|
||||||
|
formik.errors.confirmPassword && formik.touched.confirmPassword
|
||||||
|
? formik.errors.confirmPassword
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
handleTogglePasswordVisibility('confirmPassword')
|
||||||
|
}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{!showConfirmPassword ? (
|
||||||
|
<VisibilityOffOutlined />
|
||||||
|
) : (
|
||||||
|
<VisibilityOutlined />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<LoadingButton
|
||||||
|
size="large"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
className={classes.submitButton}
|
||||||
|
loading={formik.isSubmitting}
|
||||||
|
>
|
||||||
|
Save New Password
|
||||||
|
</LoadingButton>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetPasswordForm;
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Box, Card, Grid, Typography } from '@mui/material';
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import ResetPasswordForm from './ResetPasswordForm';
|
||||||
|
import { useStyles } from './resetPasswordStyles';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import ResetPasswordSuccessIcon from '../../assets/images/icon/ResetPasswordSuccessIcon.png';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
|
||||||
|
function ResetPassword() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [resetPasswordLinkSend, setResetPasswordLinkSend] = useState(false);
|
||||||
|
const handleFormSubmit = () => {
|
||||||
|
setResetPasswordLinkSend(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get URL parameters
|
||||||
|
const getQueryParams = () => new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
// Determine if we are resetting or creating a password based on URL params
|
||||||
|
const queryParams = getQueryParams();
|
||||||
|
const isCreatePassword = queryParams.get('createPassword') === 'true';
|
||||||
|
|
||||||
|
const headingText = !isCreatePassword ? 'Reset' : 'Create';
|
||||||
|
const successMessage = !isCreatePassword
|
||||||
|
? 'Password Reset Successful'
|
||||||
|
: 'Create Password Successful';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={classes.root}>
|
||||||
|
<Card className={classes.card}>
|
||||||
|
<Grid container display="flex" justifyContent="center">
|
||||||
|
{!resetPasswordLinkSend ? (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
className={classes.forgotPasswordHeader}
|
||||||
|
>
|
||||||
|
<Grid item xs={2}></Grid>
|
||||||
|
<Grid item xs={10}>
|
||||||
|
<Typography variant="Gilroy-bold" className={classes.heading}>
|
||||||
|
{headingText} your Password
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<ResetPasswordForm onSubmit={handleFormSubmit} />
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Box paddingTop="35px" paddingBottom="5px" fontSize="0.8rem">
|
||||||
|
<Box className={classes.successIcon}>
|
||||||
|
<img
|
||||||
|
className={classes.resetPasswordSuccessIcon}
|
||||||
|
src={ResetPasswordSuccessIcon}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Typography
|
||||||
|
variant="Gilroy-SemiBold"
|
||||||
|
className={classes.resetPasswordheading}
|
||||||
|
marginBottom="20px"
|
||||||
|
>
|
||||||
|
{successMessage}
|
||||||
|
</Typography>
|
||||||
|
<Grid item width="80%" margin="auto">
|
||||||
|
<Typography
|
||||||
|
variant="Gilroy-normal"
|
||||||
|
className={classes.subheading}
|
||||||
|
>
|
||||||
|
Awesome, you have successfully{' '}
|
||||||
|
{!isCreatePassword ? 'updated' : 'created'} your password.
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<LoadingButton
|
||||||
|
className={classes.submitButton}
|
||||||
|
size="small"
|
||||||
|
width="60%"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => navigate('/auth/login')}
|
||||||
|
>
|
||||||
|
Back to Login
|
||||||
|
</LoadingButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetPassword;
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import backgroundImage from '../../assets/images/background/background.jpg';
|
||||||
|
import { pxToRem } from '../../theme/typography';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
height: '100vh',
|
||||||
|
backgroundImage: `url(${backgroundImage})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
width: '370px',
|
||||||
|
maxHeight: '835px',
|
||||||
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
width: '500px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
width: '500px',
|
||||||
|
maxHeight: '735px',
|
||||||
|
},
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderRadius: theme.spacing(3.8),
|
||||||
|
opacity: '0.95',
|
||||||
|
},
|
||||||
|
forgotPasswordHeader: {
|
||||||
|
marginTop: theme.spacing(4.5),
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
textAlign: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: '65%',
|
||||||
|
},
|
||||||
|
formBody: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
// height: '162.5px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
// height: '234px',
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
// height: '325px',
|
||||||
|
},
|
||||||
|
|
||||||
|
'& .MuiTextField-root': {
|
||||||
|
marginTop: theme.spacing(0.4),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
marginTop: theme.spacing(0.4),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
marginTop: theme.spacing(0.4),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
marginTop: theme.spacing(0.4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formHeader: {
|
||||||
|
marginTop: theme.spacing(1.2),
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
fontFamily: 'Inter-Bold',
|
||||||
|
// width: '70%',
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
successIcon: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: theme.spacing(1.1),
|
||||||
|
},
|
||||||
|
resetPasswordheading: {
|
||||||
|
// width: '70%',
|
||||||
|
display: 'flex',
|
||||||
|
fontFamily: 'Inter-Bold',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: theme.spacing(3.8),
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
fontSize: pxToRem(27),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subHeading: {
|
||||||
|
width: '90%',
|
||||||
|
lineHeight: 1.1,
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
marginBottom: theme.spacing(1.0),
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
fontSize: pxToRem(13),
|
||||||
|
marginTop: theme.spacing(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resetPasswordTextField: {
|
||||||
|
marginTop: '0px',
|
||||||
|
},
|
||||||
|
confirmResetBox: {
|
||||||
|
marginTop: theme.spacing(1.2),
|
||||||
|
},
|
||||||
|
passwordCheckListTitle: {
|
||||||
|
color: 'grey',
|
||||||
|
fontSize: pxToRem(14.4),
|
||||||
|
fontStyle: 'italic',
|
||||||
|
textAlign: 'left',
|
||||||
|
marginBottom: theme.spacing(0.4),
|
||||||
|
},
|
||||||
|
passwordCheckList: {
|
||||||
|
color: 'grey',
|
||||||
|
fontSize: pxToRem(14.4),
|
||||||
|
paddingTop: '2px',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
marginBottom: '0px',
|
||||||
|
fontSize: pxToRem(3.2),
|
||||||
|
textAlign: 'left',
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
// paddingBottom: '20px',
|
||||||
|
},
|
||||||
|
inputTitle: {
|
||||||
|
'&.MuiTypography-body1': {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
},
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
// opacity: '0.54',
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
marginBottom: theme.spacing(0.6),
|
||||||
|
marginLeft: theme.spacing(0.6),
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
fontFamily: 'Inter-Regular',
|
||||||
|
fontWeight: 500,
|
||||||
|
marginTop: theme.spacing(2.2),
|
||||||
|
marginBottom: theme.spacing(4.0),
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '20px',
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
padding: '20px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('lg')]: {
|
||||||
|
padding: '20px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up('xl')]: {
|
||||||
|
height: '10px',
|
||||||
|
fontSize: pxToRem(12.8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resetPasswordSuccessIcon: {
|
||||||
|
width: '17%',
|
||||||
|
},
|
||||||
|
sendEmailText: {
|
||||||
|
marginTop: theme.spacing(2.0),
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: pxToRem(10.4),
|
||||||
|
},
|
||||||
|
emailText: {
|
||||||
|
lineHeight: 1.1,
|
||||||
|
fontSize: pxToRem(12.48),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -18,7 +18,6 @@ import {
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Paper,
|
Paper,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
Table,
|
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
|
|
@ -26,18 +25,26 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from "@mui/material";
|
||||||
import React, { useState } from 'react';
|
import React, { useMemo, useRef, useState } from "react";
|
||||||
import CustomBreadcrumbs from '../../components/CustomBreadcrumbs';
|
import CustomBreadcrumbs from "../../components/CustomBreadcrumbs";
|
||||||
import PageHeader from '../../components/PageHeader';
|
import PageHeader from "../../components/PageHeader";
|
||||||
|
import { createAdmin, getAdmins } from "../../services/auth.services";
|
||||||
|
import { pushNotification } from "../../utils/notification";
|
||||||
|
import { NOTIFICATION } from "../../constants";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { useStyles } from "./staffStyles";
|
||||||
|
import Table from "../../components/Table";
|
||||||
|
|
||||||
const StaffManagement = () => {
|
const StaffManagement = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const classes = useStyles();
|
||||||
|
const ref = useRef(null);
|
||||||
// State for form fields
|
// State for form fields
|
||||||
const [firstName, setFirstName] = useState('');
|
const [firstName, setFirstName] = useState("");
|
||||||
const [lastName, setLastName] = useState('');
|
const [lastName, setLastName] = useState("");
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState("");
|
||||||
const [emailError, setEmailError] = useState('');
|
const [emailError, setEmailError] = useState("");
|
||||||
const queryParams = new URLSearchParams(location.search);
|
|
||||||
|
|
||||||
// State for staff list
|
// State for staff list
|
||||||
const [staffList, setStaffList] = useState([]);
|
const [staffList, setStaffList] = useState([]);
|
||||||
|
|
@ -46,7 +53,7 @@ const StaffManagement = () => {
|
||||||
const [openDialog, setOpenDialog] = useState(false);
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
|
||||||
// State for search
|
// State for search
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
// State for pagination
|
// State for pagination
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
@ -55,8 +62,8 @@ const StaffManagement = () => {
|
||||||
// State for notification
|
// State for notification
|
||||||
const [notification, setNotification] = useState({
|
const [notification, setNotification] = useState({
|
||||||
open: false,
|
open: false,
|
||||||
message: '',
|
message: "",
|
||||||
severity: 'success',
|
severity: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Email validation function
|
// Email validation function
|
||||||
|
|
@ -73,41 +80,37 @@ const StaffManagement = () => {
|
||||||
const handleCloseDialog = () => {
|
const handleCloseDialog = () => {
|
||||||
setOpenDialog(false);
|
setOpenDialog(false);
|
||||||
// Clear form
|
// Clear form
|
||||||
setFirstName('');
|
setFirstName("");
|
||||||
setLastName('');
|
setLastName("");
|
||||||
setEmail('');
|
setEmail("");
|
||||||
setEmailError('');
|
setEmailError("");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Validate email
|
// Validate email
|
||||||
if (!validateEmail(email)) {
|
if (!validateEmail(email)) {
|
||||||
setEmailError('Please enter a valid email address');
|
setEmailError("Please enter a valid email address");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new staff member
|
const payload = {
|
||||||
const newStaff = {
|
username: `${firstName} ${lastName}`,
|
||||||
id: staffList.length + 1,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
email,
|
||||||
};
|
};
|
||||||
|
|
||||||
setStaffList([...staffList, newStaff]);
|
const response = await createAdmin(payload);
|
||||||
|
|
||||||
|
if (response?.data?.data) {
|
||||||
|
pushNotification("Admin created successfully!", NOTIFICATION.SUCCESS);
|
||||||
|
} else {
|
||||||
|
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
// Close dialog
|
// Close dialog
|
||||||
handleCloseDialog();
|
handleCloseDialog();
|
||||||
|
|
||||||
// Show success notification
|
|
||||||
setNotification({
|
|
||||||
open: true,
|
|
||||||
message: 'Staff member added successfully!',
|
|
||||||
severity: 'success',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle notification close
|
// Handle notification close
|
||||||
|
|
@ -118,195 +121,99 @@ const StaffManagement = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getData = async (filters) => {
|
||||||
|
try {
|
||||||
|
// Remove the type parameter since it's not defined
|
||||||
|
let params = {
|
||||||
|
...filters
|
||||||
|
};
|
||||||
|
|
||||||
|
const resp = await getAdmins(params);
|
||||||
|
console.log('API Response:', resp);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: resp?.data?.data?.data,
|
||||||
|
rowCount: resp?.data?.total || 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching admins:', error);
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
rowCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ...................breadcrumbs array........................
|
// ...................breadcrumbs array........................
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: "Dashboard",
|
||||||
path: '/',
|
path: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Staff Management',
|
label: "Staff Management",
|
||||||
path: '/staff',
|
path: "/staff",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
const columns = useMemo(() => [
|
||||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
{
|
||||||
{/* <Typography
|
size: 100,
|
||||||
variant="h4"
|
header: "Sr. No.",
|
||||||
component="h1"
|
Cell: (props) => {
|
||||||
gutterBottom
|
const tableState = props?.table?.getState();
|
||||||
sx={{ fontWeight: 'bold', mb: 4, color: '#0a2d6b' }}
|
const serialNumber = (
|
||||||
>
|
props?.row?.index +
|
||||||
Staff Management
|
1 +
|
||||||
</Typography> */}
|
tableState?.pagination?.pageIndex * tableState?.pagination?.pageSize
|
||||||
|
)
|
||||||
|
?.toString()
|
||||||
|
?.padStart(2, "0");
|
||||||
|
return <span>{serialNumber}</span>;
|
||||||
|
},
|
||||||
|
enableSorting: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
size: 280,
|
||||||
|
accessorKey: "username",
|
||||||
|
header: "User Name",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
size: 220,
|
||||||
|
accessorKey: "email",
|
||||||
|
header: "User Email Address",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
pageTitle="Staff Management"
|
pageTitle="Staff Management"
|
||||||
hideAddButton
|
// hideAddButton
|
||||||
// addButtonTitle="Add Staff"
|
addButtonIcon={<AddIcon />}
|
||||||
// onAddButtonClick={toggle}
|
onAddButtonClick={handleOpenDialog}
|
||||||
// addButtonIcon={<img src={AddIcon} />}
|
|
||||||
// addButtonDisabled={false}
|
|
||||||
// permissionName={'CREATE_USERS'}
|
|
||||||
// infiniteDropdown
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
|
<CustomBreadcrumbs breadcrumbs={breadcrumbs} />
|
||||||
|
<Box className={classes.tableMainDiv}>
|
||||||
<Paper elevation={3} sx={{ p: 0, mb: 4, overflow: 'hidden' }}>
|
<Box>
|
||||||
{/* Staff List Header with Add Button */}
|
<Table
|
||||||
<Box
|
hideTopToolbar
|
||||||
sx={{
|
columns={columns}
|
||||||
display: 'flex',
|
getData={getData}
|
||||||
justifyContent: 'space-between',
|
options={{
|
||||||
alignItems: 'center',
|
enableRowSelection: true,
|
||||||
p: 3,
|
showTopBar: false,
|
||||||
}}
|
showFilters: true,
|
||||||
>
|
|
||||||
<Typography variant="h6" component="h2" sx={{ fontWeight: 'bold' }}>
|
|
||||||
Staff List
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
startIcon={<AddIcon />}
|
|
||||||
onClick={handleOpenDialog}
|
|
||||||
sx={{
|
|
||||||
borderRadius: 50,
|
|
||||||
textTransform: 'none',
|
|
||||||
backgroundColor: '#ff3366',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#e61653',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add Admin
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Search Box */}
|
|
||||||
<Box sx={{ px: 3, pb: 2 }}>
|
|
||||||
<TextField
|
|
||||||
placeholder="Search user here"
|
|
||||||
fullWidth
|
|
||||||
size="small"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<InputAdornment position="start">
|
|
||||||
<SearchIcon color="action" />
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: 2,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
|
searchText="Staff"
|
||||||
|
showSearchBox={true}
|
||||||
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Staff List Table */}
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow sx={{ backgroundColor: '#f5f5f5' }}>
|
|
||||||
<TableCell sx={{ fontWeight: 'bold' }}>Sr. No.</TableCell>
|
|
||||||
<TableCell sx={{ fontWeight: 'bold' }}>User Name</TableCell>
|
|
||||||
<TableCell sx={{ fontWeight: 'bold' }}>
|
|
||||||
User Email Address
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{staffList.length > 0 ? (
|
|
||||||
staffList
|
|
||||||
.filter(
|
|
||||||
(staff) =>
|
|
||||||
`${staff.firstName} ${staff.lastName}`
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
|
||||||
staff.email
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase())
|
|
||||||
)
|
|
||||||
.slice((page - 1) * rowsPerPage, page * rowsPerPage)
|
|
||||||
.map((staff, index) => (
|
|
||||||
<TableRow key={staff.id}>
|
|
||||||
<TableCell>
|
|
||||||
{(page - 1) * rowsPerPage + index + 1}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{`${staff.firstName} ${staff.lastName}`}</TableCell>
|
|
||||||
<TableCell>{staff.email}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={3}
|
|
||||||
align="center"
|
|
||||||
sx={{ py: 5, color: '#666' }}
|
|
||||||
>
|
|
||||||
No staff members added yet
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
{staffList.length > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
p: 2,
|
|
||||||
borderTop: '1px solid #eee',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
|
|
||||||
disabled={page === 1}
|
|
||||||
startIcon={<ArrowBackIosNewIcon fontSize="small" />}
|
|
||||||
sx={{ mx: 1, color: '#666' }}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
disableElevation
|
|
||||||
sx={{
|
|
||||||
mx: 1,
|
|
||||||
minWidth: '36px',
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
color: '#333',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: '#e0e0e0',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{page}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={() => setPage((prev) => prev + 1)}
|
|
||||||
disabled={page * rowsPerPage >= staffList.length}
|
|
||||||
endIcon={<ArrowForwardIosIcon fontSize="small" />}
|
|
||||||
sx={{ mx: 1, color: '#666' }}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
<Box className={classes.tableMainDiv}>
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* Add Staff Dialog */}
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={openDialog}
|
open={openDialog}
|
||||||
onClose={handleCloseDialog}
|
onClose={handleCloseDialog}
|
||||||
|
|
@ -315,18 +222,18 @@ const StaffManagement = () => {
|
||||||
>
|
>
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
pb: 1,
|
pb: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<PersonAddIcon sx={{ mr: 1, color: '#0a2d6b' }} />
|
<PersonAddIcon sx={{ mr: 1, color: "#0a2d6b" }} />
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
component="span"
|
component="span"
|
||||||
sx={{ fontWeight: 'bold', color: '#0a2d6b' }}
|
sx={{ fontWeight: "bold", color: "#0a2d6b" }}
|
||||||
>
|
>
|
||||||
Add New Staff
|
Add New Staff
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
@ -341,7 +248,7 @@ const StaffManagement = () => {
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="First Name *"
|
label="First Name"
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
value={firstName}
|
value={firstName}
|
||||||
|
|
@ -354,7 +261,7 @@ const StaffManagement = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Last Name *"
|
label="Last Name"
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
value={lastName}
|
value={lastName}
|
||||||
|
|
@ -367,14 +274,14 @@ const StaffManagement = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Email Address *"
|
label="Email Address"
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
setEmailError('');
|
setEmailError("");
|
||||||
}}
|
}}
|
||||||
placeholder="Email Address"
|
placeholder="Email Address"
|
||||||
error={!!emailError}
|
error={!!emailError}
|
||||||
|
|
@ -395,9 +302,9 @@ const StaffManagement = () => {
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
sx={{
|
sx={{
|
||||||
py: 1.5,
|
py: 1.5,
|
||||||
backgroundColor: '#ff3366',
|
backgroundColor: "#ff3366",
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
backgroundColor: '#e61653',
|
backgroundColor: "#e61653",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -405,23 +312,201 @@ const StaffManagement = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
// <Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
|
// {/* <Typography
|
||||||
|
// variant="h4"
|
||||||
|
// component="h1"
|
||||||
|
// gutterBottom
|
||||||
|
// sx={{ fontWeight: 'bold', mb: 4, color: '#0a2d6b' }}
|
||||||
|
// >
|
||||||
|
// Staff Management
|
||||||
|
// </Typography> */}
|
||||||
|
|
||||||
{/* Notification */}
|
// <PageHeader
|
||||||
<Snackbar
|
// pageTitle="Staff Management"
|
||||||
open={notification.open}
|
// hideAddButton
|
||||||
autoHideDuration={4000}
|
// // addButtonTitle="Add Staff"
|
||||||
onClose={handleCloseNotification}
|
// // onAddButtonClick={toggle}
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
// // addButtonIcon={<img src={AddIcon} />}
|
||||||
>
|
// // addButtonDisabled={false}
|
||||||
<Alert
|
// // permissionName={'CREATE_USERS'}
|
||||||
onClose={handleCloseNotification}
|
// // infiniteDropdown
|
||||||
severity={notification.severity}
|
// />
|
||||||
sx={{ width: '100%' }}
|
|
||||||
>
|
// <CustomBreadcrumbs breadcrumbs={breadcrumbs} />
|
||||||
{notification.message}
|
|
||||||
</Alert>
|
// <Paper elevation={3} sx={{ p: 0, mb: 4, overflow: 'hidden' }}>
|
||||||
</Snackbar>
|
// {/* Staff List Header with Add Button */}
|
||||||
</Container>
|
// <Box
|
||||||
|
// sx={{
|
||||||
|
// display: 'flex',
|
||||||
|
// justifyContent: 'space-between',
|
||||||
|
// alignItems: 'center',
|
||||||
|
// p: 3,
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Typography variant="h6" component="h2" sx={{ fontWeight: 'bold' }}>
|
||||||
|
// Staff List
|
||||||
|
// </Typography>
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// variant="contained"
|
||||||
|
// color="error"
|
||||||
|
// startIcon={<AddIcon />}
|
||||||
|
// onClick={handleOpenDialog}
|
||||||
|
// sx={{
|
||||||
|
// borderRadius: 50,
|
||||||
|
// textTransform: 'none',
|
||||||
|
// backgroundColor: '#ff3366',
|
||||||
|
// '&:hover': {
|
||||||
|
// backgroundColor: '#e61653',
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Add Admin
|
||||||
|
// </Button>
|
||||||
|
// </Box>
|
||||||
|
|
||||||
|
// {/* Search Box */}
|
||||||
|
// <Box sx={{ px: 3, pb: 2 }}>
|
||||||
|
// <TextField
|
||||||
|
// placeholder="Search user here"
|
||||||
|
// fullWidth
|
||||||
|
// size="small"
|
||||||
|
// value={searchQuery}
|
||||||
|
// onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
// InputProps={{
|
||||||
|
// startAdornment: (
|
||||||
|
// <InputAdornment position="start">
|
||||||
|
// <SearchIcon color="action" />
|
||||||
|
// </InputAdornment>
|
||||||
|
// ),
|
||||||
|
// }}
|
||||||
|
// sx={{
|
||||||
|
// backgroundColor: '#fff',
|
||||||
|
// '& .MuiOutlinedInput-root': {
|
||||||
|
// borderRadius: 2,
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </Box>
|
||||||
|
|
||||||
|
// {/* Staff List Table */}
|
||||||
|
// <TableContainer>
|
||||||
|
// <Table>
|
||||||
|
// <TableHead>
|
||||||
|
// <TableRow sx={{ backgroundColor: '#f5f5f5' }}>
|
||||||
|
// <TableCell sx={{ fontWeight: 'bold' }}>Sr. No.</TableCell>
|
||||||
|
// <TableCell sx={{ fontWeight: 'bold' }}>User Name</TableCell>
|
||||||
|
// <TableCell sx={{ fontWeight: 'bold' }}>
|
||||||
|
// User Email Address
|
||||||
|
// </TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// </TableHead>
|
||||||
|
// <TableBody>
|
||||||
|
// {staffList.length > 0 ? (
|
||||||
|
// staffList
|
||||||
|
// .filter(
|
||||||
|
// (staff) =>
|
||||||
|
// `${staff.firstName} ${staff.lastName}`
|
||||||
|
// .toLowerCase()
|
||||||
|
// .includes(searchQuery.toLowerCase()) ||
|
||||||
|
// staff.email
|
||||||
|
// .toLowerCase()
|
||||||
|
// .includes(searchQuery.toLowerCase())
|
||||||
|
// )
|
||||||
|
// .slice((page - 1) * rowsPerPage, page * rowsPerPage)
|
||||||
|
// .map((staff, index) => (
|
||||||
|
// <TableRow key={staff.id}>
|
||||||
|
// <TableCell>
|
||||||
|
// {(page - 1) * rowsPerPage + index + 1}
|
||||||
|
// </TableCell>
|
||||||
|
// <TableCell>{`${staff.firstName} ${staff.lastName}`}</TableCell>
|
||||||
|
// <TableCell>{staff.email}</TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// ))
|
||||||
|
// ) : (
|
||||||
|
// <TableRow>
|
||||||
|
// <TableCell
|
||||||
|
// colSpan={3}
|
||||||
|
// align="center"
|
||||||
|
// sx={{ py: 5, color: '#666' }}
|
||||||
|
// >
|
||||||
|
// No staff members added yet
|
||||||
|
// </TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// )}
|
||||||
|
// </TableBody>
|
||||||
|
// </Table>
|
||||||
|
// </TableContainer>
|
||||||
|
|
||||||
|
// {/* Pagination */}
|
||||||
|
// {staffList.length > 0 && (
|
||||||
|
// <Box
|
||||||
|
// sx={{
|
||||||
|
// display: 'flex',
|
||||||
|
// justifyContent: 'center',
|
||||||
|
// p: 2,
|
||||||
|
// borderTop: '1px solid #eee',
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// <Button
|
||||||
|
// onClick={() => setPage((prev) => Math.max(prev - 1, 1))}
|
||||||
|
// disabled={page === 1}
|
||||||
|
// startIcon={<ArrowBackIosNewIcon fontSize="small" />}
|
||||||
|
// sx={{ mx: 1, color: '#666' }}
|
||||||
|
// >
|
||||||
|
// Previous
|
||||||
|
// </Button>
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// variant="contained"
|
||||||
|
// disableElevation
|
||||||
|
// sx={{
|
||||||
|
// mx: 1,
|
||||||
|
// minWidth: '36px',
|
||||||
|
// backgroundColor: '#f0f0f0',
|
||||||
|
// color: '#333',
|
||||||
|
// '&:hover': {
|
||||||
|
// backgroundColor: '#e0e0e0',
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// {page}
|
||||||
|
// </Button>
|
||||||
|
|
||||||
|
// <Button
|
||||||
|
// onClick={() => setPage((prev) => prev + 1)}
|
||||||
|
// disabled={page * rowsPerPage >= staffList.length}
|
||||||
|
// endIcon={<ArrowForwardIosIcon fontSize="small" />}
|
||||||
|
// sx={{ mx: 1, color: '#666' }}
|
||||||
|
// >
|
||||||
|
// Next
|
||||||
|
// </Button>
|
||||||
|
// </Box>
|
||||||
|
// )}
|
||||||
|
// </Paper>
|
||||||
|
|
||||||
|
// {/* Add Staff Dialog */}
|
||||||
|
|
||||||
|
// {/* Notification */}
|
||||||
|
// <Snackbar
|
||||||
|
// open={notification.open}
|
||||||
|
// autoHideDuration={4000}
|
||||||
|
// onClose={handleCloseNotification}
|
||||||
|
// anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
// >
|
||||||
|
// <Alert
|
||||||
|
// onClose={handleCloseNotification}
|
||||||
|
// severity={notification.severity}
|
||||||
|
// sx={{ width: '100%' }}
|
||||||
|
// >
|
||||||
|
// {notification.message}
|
||||||
|
// </Alert>
|
||||||
|
// </Snackbar>
|
||||||
|
// </Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import { pxToRem } from '../../theme/typography';
|
||||||
|
|
||||||
|
export const useStyles = makeStyles((theme) => ({
|
||||||
|
chipClass: {
|
||||||
|
height: 'fit-content',
|
||||||
|
minHeight: '30px',
|
||||||
|
padding: '2px',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
statusColor: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontSize: pxToRem(10),
|
||||||
|
},
|
||||||
|
tabsBox: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: ' space-around',
|
||||||
|
// width: '55%',
|
||||||
|
marginTop: theme.spacing(0.5),
|
||||||
|
marginRight: theme.spacing(5.0),
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
secondaryButton: {
|
||||||
|
width: '200px',
|
||||||
|
height: '46px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
justifyContent: 'space-evenly',
|
||||||
|
fontSize: pxToRem(16),
|
||||||
|
},
|
||||||
|
tableActionIcons: {
|
||||||
|
marginRight: theme.spacing(1.4),
|
||||||
|
width: '15px',
|
||||||
|
},
|
||||||
|
companyNameTableColumn: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
companyName: {
|
||||||
|
marginLeft: theme.spacing(1),
|
||||||
|
fontSize: pxToRem(14),
|
||||||
|
objectFit: 'contain',
|
||||||
|
width: '260px',
|
||||||
|
},
|
||||||
|
companyNameLink: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: theme.palette.grey[10],
|
||||||
|
'&:hover': {
|
||||||
|
color: theme.palette.info.main,
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
companyWebsiteLabel: {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
},
|
||||||
|
companyNameLogo: {
|
||||||
|
height: '40px',
|
||||||
|
width: '40px',
|
||||||
|
borderRadius: theme.shape.borderRadiusComponent,
|
||||||
|
objectFit: 'contain',
|
||||||
|
},
|
||||||
|
sendEmailStatus: {
|
||||||
|
fontSize: pxToRem(14),
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
sendEmailLastSentMailDate: {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
},
|
||||||
|
addDiscountCodeLink: {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
addDiscountCodeLabel: {
|
||||||
|
fontSize: pxToRem(14),
|
||||||
|
backgroundColor: theme.palette.common.white,
|
||||||
|
color: theme.palette.common.black,
|
||||||
|
},
|
||||||
|
customModel: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
customModelBox: {
|
||||||
|
paddingLeft: theme.spacing(5),
|
||||||
|
paddingRight: theme.spacing(5),
|
||||||
|
textAlign: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
// height: '100%',
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
newPlanTitleText: {
|
||||||
|
fontSize: pxToRem(28),
|
||||||
|
fontFamily: theme.fontFamily.bold,
|
||||||
|
},
|
||||||
|
newPlanSubTitleText: {
|
||||||
|
fontSize: pxToRem(18),
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
},
|
||||||
|
addPlanSuccessIcon: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
paddingTop: theme.spacing(1),
|
||||||
|
},
|
||||||
|
planAddedText: {
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
color: theme.palette.grey[54],
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
alignSelf: 'center',
|
||||||
|
},
|
||||||
|
verifyIcon: {
|
||||||
|
marginLeft: theme.spacing(0.3),
|
||||||
|
fontSize: pxToRem(12),
|
||||||
|
color: theme.palette.grey[54],
|
||||||
|
},
|
||||||
|
}));
|
||||||
Loading…
Reference in New Issue