Browse Source

added alert dialog

pull/60/head
meris.ahmatovic 3 years ago
parent
commit
83e16f2b86

+ 0
- 8
src/App.test.js View File

@@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

+ 19
- 14
src/assets/styles/_typography.scss View File

@@ -73,32 +73,37 @@ h5 {
text-align: center !important;
}

.text-black{
.text-black {
color: $mainBlack;
}

.text-blue{
.text-blue {
color: $mainBlue;
}

.text-grey9d{
.text-grey9d {
color: $grey-9D;
}
.text-uppercase{
.text-uppercase {
text-transform: uppercase !important;
}
.page-heading{
.page-heading {
font-style: normal;
font-weight: 600;
font-size: 36px;
line-height: 32px;
/* identical to box height, or 89% */
font-weight: 600;
font-size: 36px;
line-height: 32px;
/* identical to box height, or 89% */

letter-spacing: 0.02em;
letter-spacing: 0.02em;

/* Main Black */
/* Main Black */

color: #272727;@include media-below($bp-xl) {
font-size: 18px !important;
color: #272727;
@include media-below($bp-xl) {
font-size: 18px !important;
}
}
.highlighted{
background: $mainBlue;
color: #fff;
}
}

+ 17
- 0
src/assets/styles/components/_modal.scss View File

@@ -167,3 +167,20 @@ $header-height-mobile: pxToRemMd(74px);
flex: 1 1 auto;
overflow: auto;
}
.dialog-btn{
text-transform: uppercase;
letter-spacing: 1px !important;
font-size: 12px !important;
min-width: 200px !important;
}

.dialog-btn:last-of-type{
color: $white;
background-color: $mainBlue;
}
.modal-content{
padding: 25px 0px 15px 0px;
font-size: 16px;
color: $mainBlack;
text-align: center;
}

+ 4
- 5
src/assets/styles/components/_usersPage.scss View File

@@ -38,11 +38,10 @@ padding-left: 35px;
gap: 9px;
}
.inviteBtn {
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
padding: 18px 70px !important;
margin-top: 60px;
text-transform: capitalize;
padding: 10px 40px !important;
margin-left: 20px;
font-size: 16px !important;
}
.secondaryRow:hover {
background-color: $mainBlueLight;

+ 1
- 0
src/components/IconButton/IconButton.js View File

@@ -13,6 +13,7 @@ const IconButton = ({ children, onClick, className }) => {

return (
<button
data-testid="btn-testid"
type="button"
ref={buttonRef}
onClick={handleClick}

+ 107
- 0
src/components/MUI/ConfirmDialog.js View File

@@ -0,0 +1,107 @@
import React from "react";
import PropTypes from "prop-types";
import {
Dialog,
DialogTitle,
DialogActions,
useMediaQuery,
useTheme,
DialogContent,
} from "@mui/material";
import IconButton from "../IconButton/IconButton";

const ConfirmDialog = ({
title,
subtitle,
imgSrc,
content,
onConfirm,
onClose,
open,
maxWidth,
fullWidth,
responsive,
}) => {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));

// const { t } = useTranslation();

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

// useEffect(()=>{
// handleClose();
// },[onConfirm])

return (
<Dialog
maxWidth={maxWidth}
fullWidth={fullWidth}
fullScreen={responsive && fullScreen}
onClose={handleClose}
open={open}
style={{
padding: "36px",
}}
>
<div style={{ padding: "36px" }}>
<DialogTitle style={{ padding: 0 }}>
<div
className="flex-center"
style={{ justifyContent: "space-between" }}
>
<div className="flex-center" style={{ justifyContent: "start" }}>
<img
style={{
position: "relative",
top: -0.25,
paddingRight: "10px",
}}
src={imgSrc}
/>
<h5>{title}</h5>
<div className="vr"></div>
<p className="dialog-subtitle">{subtitle}</p>
</div>
</div>
</DialogTitle>
<DialogContent>
<div className="modal-content">{content}</div>
</DialogContent>
<DialogActions style={{ padding: 0 }}>
<IconButton
data-testid="editbtn"
className={`c-btn--primary-outlined c-btn dialog-btn`}
onClick={onClose}
>
Cancel
</IconButton>
<IconButton
data-testid="editbtn"
className={`c-btn--primary-outlined c-btn dialog-btn`}
onClick={onConfirm}
>
Confirm
</IconButton>
</DialogActions>
</div>
</Dialog>
);
};

ConfirmDialog.propTypes = {
title: PropTypes.any,
subtitle: PropTypes.any,
imgSrc: PropTypes.any,
open: PropTypes.bool.isRequired,
content: PropTypes.any,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
maxWidth: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl"]),
fullWidth: PropTypes.bool,
responsive: PropTypes.bool,
};

export default ConfirmDialog;

+ 141
- 61
src/pages/UsersPage/UserDetails.js View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import avatar from "../../assets/images/Avatar.png";
import filters from "../../assets/images/filters.png";
@@ -6,40 +6,66 @@ import lock from "../../assets/images/lock.png";
import forbiden from "../../assets/images/forbiden.png";
import IconButton from "../../components/IconButton/IconButton";
import { Link, useParams } from "react-router-dom";
import { deleteUserReq, setEnableUsersReq, userDetailsReq } from "../../store/actions/users/usersActions";
import {
deleteUserReq,
setEnableUsersReq,
userDetailsReq,
} from "../../store/actions/users/usersActions";
import { useDispatch, useSelector } from "react-redux";
import { USERS_PAGE } from "../../constants/pages";
import { forgetPassword } from "../../store/actions/login/loginActions";
import { useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import ConfirmDialog from "../../components/MUI/ConfirmDialog";

const UserDetails = ({ history }) => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const { id } = useParams();
const dispatch = useDispatch();
const [showConfirm, setConfirm] = useState(false);
const [showReset, setReset] = useState(false);
const [showDelete, setDelete] = useState(false);

const { user } = useSelector((s) => s.userDetails);

const handleReset = (email) => {
dispatch(
forgetPassword({
email,
handleApiResponseSuccessReset,
})
);
};

const enableHandler = () =>{
dispatch(setEnableUsersReq({ id: user.id }));
}
const handleReset = (email) => {
dispatch(
forgetPassword({
email,
handleApiResponseSuccess: handleApiResponseSuccessReset,
})
);
};

const handleApiResponseSuccessReset = () => {
console.log('DONE!')
setReset(false);
};

const enableHandler = () => {
dispatch(
setEnableUsersReq({
id: user.id,
handleApiResponseSuccess: handleApiResponseSuccessEnable,
})
);
};

const handleApiResponseSuccessEnable = () => {
setConfirm(false);
};

const deleteHandler = () => {
dispatch(
deleteUserReq({
id,
handleApiResponseSuccess: handleApiResponseSuccessDelete,
})
);
};

const handleApiResponseSuccess = () => {
const handleApiResponseSuccessDelete = () => {
history.push({
pathname: USERS_PAGE,
state: {
@@ -48,18 +74,56 @@ const UserDetails = ({ history }) => {
});
};

const deleteHandler = () => {
dispatch(deleteUserReq({ id, handleApiResponseSuccess }));
};

useEffect(()=>{
dispatch(userDetailsReq({id}))
},[dispatch])
useEffect(() => {
dispatch(userDetailsReq({ id }));
}, [dispatch]);

return (
<div>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<ConfirmDialog
open={showReset}
title={"Reset password"}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={lock}
content="Are you sure you want to send password reset link?"
onClose={() => {
setReset(false);
}}
onConfirm={() => {
handleReset(user.email);
// setConfirm(false)
}}
/>
<ConfirmDialog
open={showConfirm}
title={"Disable user"}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={forbiden}
content="Are you sure you want to disable user?"
onClose={() => {
setConfirm(false);
}}
onConfirm={() => {
enableHandler(user.id);
// setConfirm(false)
}}
/>
<ConfirmDialog
open={showDelete}
title={"Delete user"}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={filters}
content="Are you sure you want to delete user?"
onClose={() => {
setDelete(false);
}}
onConfirm={() => {
deleteHandler(user.id);
// setConfirm(false)
}}
/>
<div className="pl-144 pt-36px">
<div className="divider">
<div className="flex-center">
@@ -80,7 +144,7 @@ const UserDetails = ({ history }) => {
}}
className="text-blue"
>
{user && user.firstName} {user && user.lastName}
{user && user.firstName} {user && user.lastName}
</h3>
</div>
<div className="flex-center">
@@ -88,9 +152,9 @@ const UserDetails = ({ history }) => {
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => handleReset(user.email)}
onClick={() => setReset(true)}
>
{!matches && 'Resetuj password'}
{!matches && "Resetuj password"}
<img
style={{
position: "relative",
@@ -100,38 +164,42 @@ const UserDetails = ({ history }) => {
src={lock}
/>
</IconButton>
{!matches && <IconButton
className={
`c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding ${user?.isEnabled ? 'activeEnable' : 'deactiveEnable'}`
}
onClick={enableHandler}
>
{user?.isEnabled ? 'Blokiraj' : 'Odblokiraj'}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={forbiden}
/>
</IconButton>}
{!matches && <IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={deleteHandler}
>
Obrisi
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={filters}
/>
</IconButton>}
{!matches && (
<IconButton
className={`c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding ${
user?.isEnabled ? "activeEnable" : "deactiveEnable"
}`}
onClick={() => setConfirm(true)}
>
{user?.isEnabled ? "Blokiraj" : "Odblokiraj"}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={forbiden}
/>
</IconButton>
)}
{!matches && (
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => setDelete(true)}
>
Obrisi
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={filters}
/>
</IconButton>
)}
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
@@ -156,7 +224,11 @@ const UserDetails = ({ history }) => {
width="108px"
style={{ margin: "18px 15px 36px 0px" }}
/>
<p>{user?.position ? user.position : "Position has not been declared yet."}</p>
<p>
{user?.position
? user.position
: "Position has not been declared yet."}
</p>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "18px" }}>
<p style={{ fontWeight: "600" }}>Kontakt</p>
@@ -166,7 +238,11 @@ const UserDetails = ({ history }) => {
</div>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<p style={{ width: "85px" }}>Telefon:</p>
<p className="text-blue">{user?.phoneNumber ? user.phoneNumber : "User has no phone number saved."}</p>
<p className="text-blue">
{user?.phoneNumber
? user.phoneNumber
: "User has no phone number saved."}
</p>
</div>
</div>
<div
@@ -180,7 +256,11 @@ const UserDetails = ({ history }) => {
<p style={{ fontWeight: "600" }}>Drustvene mreze</p>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<p style={{ width: "85px" }}>LinkedIn:</p>
<p className="text-blue">{user?.socialMedias ? user.socialMedias : "User takes not part in any social media."}</p>
<p className="text-blue">
{user?.socialMedias
? user.socialMedias
: "User takes not part in any social media."}
</p>
</div>
</div>
<div

+ 225
- 87
src/pages/UsersPage/UsersPage.js View File

@@ -3,7 +3,7 @@ import IconButton from "../../components/IconButton/IconButton";
import userPageBtnIcon from "../../assets/images/userPageBtnIcon.png";
import planeVector from "../../assets/images/planeVector.png";
import lock from "../../assets/images/lock.png";
import filters from "../../assets/images/filters.png";
// import filters from "../../assets/images/filters.png";
import forbiden from "../../assets/images/forbiden.png";
import x from "../../assets/images/x.png";
import edit from "../../assets/images/edit.png";
@@ -18,12 +18,13 @@ import {
setUsersReq,
} from "../../store/actions/users/usersActions";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";
import { TextField, useMediaQuery } from "@mui/material";
// import DialogComponent from "../../components/MUI/DialogComponent";
import InviteDialog from "../../components/MUI/InviteDialog";
import { Link } from "react-router-dom";
import { forgetPassword } from "../../store/actions/login/loginActions";
import { useTranslation } from "react-i18next";
import ConfirmDialog from "../../components/MUI/ConfirmDialog";

const UsersPage = () => {
const theme = useTheme();
@@ -33,6 +34,10 @@ const UsersPage = () => {

const [showInvite, setShowInvite] = useState(false);
const [editEnable, setEdit] = useState(false);
const [search, setSearch] = useState("");
const [chosen, setChosen] = useState(null);
const [showConfirm, setConfirm] = useState(false);
const [showReset, setReset] = useState(false);

const { t } = useTranslation();

@@ -41,27 +46,83 @@ const UsersPage = () => {
}, [dispatch]);

const disableHandler = (id) => {
dispatch(setEnableUsersReq({ id }));
dispatch(
setEnableUsersReq({
id,
handleApiResponseSuccess: handleApiResponseSuccessEnable,
})
);
};

const handleReset = (email) => {
dispatch(
forgetPassword({
email,
handleApiResponseSuccessReset,
handleApiResponseSuccess: handleApiResponseSuccessReset,
})
);
};

const handleApiResponseSuccessReset = () => {
console.log("DONE!");
setReset(false)
};

const handleApiResponseSuccessEnable = () => {
setConfirm(false);
};
const formatLabel = (string, value) => {
if (!value) {
return string;
}
return (
<span>
{string.split(value).reduce((prev, current, i) => {
if (!i) {
return [current];
}
return prev.concat(
<b className="highlighted" key={value + current}>
{value}
</b>,
current
);
}, [])}
</span>
);
};

return (
<div>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
{/* {showInvite && <DialogComponent/>} */}
<ConfirmDialog
open={showConfirm}
title={"Disable user"}
subtitle={chosen?.firstName + ' ' + chosen?.lastName}
imgSrc={forbiden}
content='Are you sure you want to disable user?'
onClose={() => {
setConfirm(false);
}}
onConfirm={() => {
disableHandler(chosen.id);
// setConfirm(false)
}}
/>
<ConfirmDialog
open={showReset}
title={"Reset password"}
subtitle={chosen?.firstName + ' ' + chosen?.lastName}
imgSrc={lock}
content='Are you sure you want to send password reset link?'
onClose={() => {
setReset(false);
}}
onConfirm={() => {
handleReset(chosen.email)
// setConfirm(false)
}}
/>
<InviteDialog
open={showInvite}
onClose={() => {
@@ -108,7 +169,7 @@ const UsersPage = () => {
<IconButton
className={`${
editEnable && "enabledEdit"
} c-btn--primary-outlined c-btn userPageBtn`}
} c-btn--primary-outlined editEnableBtn c-btn userPageBtn`}
onClick={() => {
setEdit((s) => !s);
}}
@@ -124,19 +185,20 @@ const UsersPage = () => {
/>
</IconButton>
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
className={"c-btn--primary c-btn inviteBtn"}
onClick={() => {
setShowInvite(true);
}}
>
{!matches && "Filteri"}
{t("users.invite")}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: matches ? "0px" : "10px",
top: 1.25,
paddingLeft: "15px",
}}
src={filters}
/>
src={planeVector}
/>{" "}
</IconButton>
</div>
</div>
@@ -151,6 +213,22 @@ const UsersPage = () => {
}}
>
<div className=" table-cont">
<TextField
name="username"
label={t("common.labelUsername")}
margin="normal"
value={search}
// value={formik.values.username}
// onChange={formik.handleChange}
// error={formik.touched.username && Boolean(formik.errors.username)}
// helperText={formik.touched.username && formik.errors.username}
// autoFocus
// fullWidth
onChange={(e) => setSearch(e.target.value)}
style={{
width: "893.56px",
}}
/>
<table className="usersTable" style={{ width: "893.56px" }}>
<thead>
<tr className="headingRow">
@@ -161,82 +239,142 @@ const UsersPage = () => {
</tr>
</thead>
<tbody>
{users.map((n) => (
<tr key={n.id} className="secondaryRow">
<td>
{n.firstName} {n.lastName}
</td>
<td>{n.email}</td>
<td>HR Specialist</td>
{/* <td className="cvLink">CV_Ermin.pdf</td> */}
<td>
{editEnable && (
<>
<IconButton
className={`c-btn--primary-outlined c-btn td-btn`}
onClick={() => handleReset(n.email)}
>
<img
style={{
position: "relative",
}}
src={lock}
/>
</IconButton>
<IconButton
className={`c-btn--primary-outlined c-btn td-btn ${
n.isEnabled ? "active" : "inactive"
}`}
onClick={() => disableHandler(n.id)}
>
<img
style={{
position: "relative",
}}
src={forbiden}
/>
</IconButton>
<Link to={`/users/${n.id}`}>
<IconButton
className={"c-btn--primary-outlined c-btn td-btn"}
>
<img
style={{
position: "relative",
{
// search !== '' ? users.map((n) => (
// <tr "user-row" key={n.id} className="secondaryRow">
// <td>
// {n.firstName} {n.lastName}
// </td>
// <td>{n.email}</td>
// <td>HR Specialist</td>
// <td>
// {editEnable && (
// <>
// <IconButton
// className={`c-btn--primary-outlined c-btn td-btn`}
// onClick={() => handleReset(n.email)}
// >
// <img
// style={{
// position: "relative",
// }}
// src={lock}
// />
// </IconButton>
// <IconButton
// className={`c-btn--primary-outlined c-btn td-btn ${
// n.isEnabled ? "active" : "inactive"
// }`}
// onClick={() => disableHandler(n.id)}
// >
// <img
// style={{
// position: "relative",
// }}
// src={forbiden}
// />
// </IconButton>
// <Link to={`/users/${n.id}`}>
// <IconButton
// className={"c-btn--primary-outlined c-btn td-btn"}
// >
// <img
// style={{
// position: "relative",
// }}
// src={edit}
// />
// </IconButton>
// </Link>
// </>
// )}
// </td>
// </tr>
// ))
// :
users
.filter((n) =>
(n.firstName + " " + n.lastName)
.toLowerCase()
.includes(search.toLowerCase())
)
.map((n) => (
<tr
key={n.id}
className="secondaryRow"
>
<td>
{(n.firstName + " " + n.lastName).includes(search) ? (
formatLabel(n.firstName + " " + n.lastName, search)
) : (
<span>{n.firstName + " " + n.lastName}</span>
)}
</td>
<td>{n.email}</td>
<td>HR Specialist</td>
<td>
{editEnable && (
<>
<IconButton
className={`c-btn--primary-outlined c-btn td-btn`}
onClick={() => {
setChosen(n);
setReset(true);
}}
>
<img
style={{
position: "relative",
}}
src={lock}
/>
</IconButton>
<IconButton
className={`c-btn--primary-outlined c-btn td-btn ${
n.isEnabled ? "active" : "inactive"
}`}
onClick={() => {
setChosen(n);
setConfirm(true);
}}
src={edit}
/>
</IconButton>
</Link>
</>
)}
</td>
</tr>
))}
>
<img
style={{
position: "relative",
}}
src={forbiden}
/>
</IconButton>
<Link to={`/users/${n.id}`}>
<IconButton
className={
"c-btn--primary-outlined c-btn td-btn"
}
>
<img
style={{
position: "relative",
}}
src={edit}
/>
</IconButton>
</Link>
</>
)}
</td>
</tr>
))
}
</tbody>
</table>
</div>
<div style={{ display: "flex", justifyContent: "flex-end", marginBottom:'35px' }}>
<IconButton
className={"c-btn--primary c-btn inviteBtn"}
onClick={() => {
setShowInvite(true);
}}
// style={{
// // marginBottom:'20px !important'
// }}
>
<img
style={{
position: "relative",
top: 1.25,
paddingRight: "15px",
}}
src={planeVector}
/>{" "}
{t("users.invite")}
</IconButton>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginBottom: "35px",
}}
></div>
</div>
</div>
</div>

+ 0
- 1
src/store/reducers/user/userDetailsReducer.js View File

@@ -35,7 +35,6 @@ function setUserDetailsErrorMessage(state, action) {
}

function toggleUserDetails(state) {
console.log('ovde')
return state.user
? {
...state,

+ 3
- 0
src/store/saga/usersSaga.js View File

@@ -50,6 +50,9 @@ export function* enableUser({ payload }) {
const result = yield call(enableUserRequest, payload.id);
yield put(setEnableUsers(result.data));
yield put(toggleSingleUser());
if(payload.handleApiResponseSuccess){
yield call(payload.handleApiResponseSuccess)
}
} catch (error) {
if (error.response && error.response.data) {
const errorMessage = yield call(rejectErrorCodeHelper, error);

Loading…
Cancel
Save