Skip to content
Snippets Groups Projects
Commit e18d7c5c authored by Léo Banno-Cloutier's avatar Léo Banno-Cloutier
Browse files

jams-react-client: fix DisplayUserProfile and Devices pages

This fix lets users that are not admin see the pages as
intended. I also refactored the 2 pages into smaller components.

Change-Id: Ibc02534ea1215d870772f6f2fd1061a87b56ed38
parent 59c57b7e
No related branches found
No related tags found
No related merge requests found
import React, { useEffect, useState } from "react";
import React, { FC, useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import classnames from "classnames";
import { Formik, Field } from "formik";
// @mui/material components
import { makeStyles } from "@mui/styles";
import Tooltip from "@mui/material/Tooltip";
import IconButton from "@mui/material/IconButton";
import Button from "components/CustomButtons/Button";
import Table from "@mui/material/Table";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import TextField from "@mui/material/TextField";
// @mui/icons-material
import Edit from "@mui/icons-material/Edit";
......@@ -37,79 +29,67 @@ import {
import axios from "axios";
import i18next from "i18next";
import EditDeviceDialog from "./EditDeviceDialog";
import RevokeDeviceDialog from "./RevokeDeviceDialog";
const useStyles = makeStyles(styles);
export default function Devices({ username }) {
interface DevicesProps {
username: string;
}
export interface Device {
deviceId: string;
displayName: string;
revoked: boolean;
}
const Devices: FC<DevicesProps> = ({ username }) => {
const classes = useStyles();
const history = useHistory();
const [devices, setDevices] = useState([]);
const [selectedDevice, setSelectedDevice] = useState({});
const [devices, setDevices] = useState<Device[]>([]);
const [selectedDevice, setSelectedDevice] = useState<Device>({});
const [displayName, setDisplayName] = useState("");
const [openEdit, setOpenEdit] = useState(false);
const [openRevoke, setOpenRevoke] = useState(false);
const userData = { username };
useEffect(() => {
auth.checkDirectoryType(() => {
if (auth.hasAdminScope()) {
axios(configApiCall(api_path_get_admin_devices, "GET", userData, null))
.then((response) => {
if (response.data.length === 0) {
setDevices([]);
} else {
setDevices(response.data);
}
})
.catch((error) => {
console.log(error);
if (error.response.status === 401) {
auth.authenticated = false;
history.push("/signin");
}
});
} else {
axios(configApiCall(api_path_get_auth_devices, "GET", null, null))
.then((response) => {
if (response.data.length === 0) {
setDevices([]);
} else {
var resultSet = JSON.parse(
response.data.replace(/\s+/g, " ").trim()
);
setDevices(resultSet);
}
})
.catch((error) => {
if (error.response.status === 401) {
auth.authenticated = false;
history.push("/signin");
}
});
}
const requestConfig = auth.hasAdminScope()
? configApiCall(api_path_get_admin_devices, "GET", { username }, null)
: configApiCall(api_path_get_auth_devices, "GET", null, null);
axios(requestConfig)
.then((response) => {
if (response.data.length === 0) {
setDevices([]);
} else {
setDevices(response.data);
}
})
.catch((error) => {
console.log(error);
if (error.response.status === 401) {
auth.authenticated = false;
history.push("/signin");
}
});
});
}, []);
function getDeviceStatus(device) {
if (!device.revoked) {
return "Active";
} else {
return "Revoked";
}
}
function getDeviceId(device) {
return device.deviceId;
}
const getDeviceStatus = (device: Device) => {
return device.revoked ? "Revoked" : "Active";
};
const handleClickEdit = (e, device) => {
const handleClickEdit = (e: MouseEvent, device: Device) => {
e.preventDefault();
setOpenEdit(true);
setSelectedDevice(device);
};
const handleClickRevoke = (e, device) => {
const handleClickRevoke = (e: MouseEvent, device: Device) => {
e.preventDefault();
setOpenRevoke(true);
setSelectedDevice(device);
......@@ -120,119 +100,72 @@ export default function Devices({ username }) {
setOpenRevoke(false);
setSelectedDevice({});
};
const handleCancel = () => {
setOpenEdit(false);
setOpenRevoke(false);
setSelectedDevice({});
};
const handleUpdate = () => {
let requestConfig;
if (auth.hasAdminScope()) {
const data = {
username,
deviceId: selectedDevice.deviceId,
deviceName: displayName,
};
axios(
configApiCall(
api_path_delete_admin_device_revoke +
"?username=" +
data.username +
"&deviceId=" +
data.deviceId +
"&deviceName=" +
data.deviceName,
"PUT",
null,
null
)
)
.then(() => {
setSelectedDevice({});
setOpenEdit(false);
})
.catch((error) => {
console.log(error);
});
requestConfig = configApiCall(
api_path_delete_admin_device_revoke +
"?username=" +
username +
"&deviceId=" +
selectedDevice.deviceId +
"&deviceName=" +
displayName,
"PUT",
null,
null
);
} else {
const data = {
deviceName: displayName,
};
axios(
configApiCall(
api_path_rename_device +
"/" +
selectedDevice.deviceId +
"?deviceName=" +
data.deviceName,
"PUT",
null,
null
)
)
.then(() => {
setSelectedDevice({});
setOpenEdit(false);
})
.catch((error) => {
console.log(error);
});
requestConfig = configApiCall(
api_path_rename_device +
"/" +
selectedDevice.deviceId +
"?deviceName=" +
displayName,
"PUT",
null,
null
);
}
axios(requestConfig)
.then(() => {
setSelectedDevice({});
setOpenEdit(false);
})
.catch((error) => {
console.log(error);
});
};
const handleDeviceRevoke = () => {
let requestConfig;
if (auth.hasAdminScope()) {
const data = {
username,
deviceId: selectedDevice.deviceId,
};
axios(
configApiCall(
api_path_delete_admin_device_revoke +
"?username=" +
data.username +
"&deviceId=" +
data.deviceId,
"DELETE",
null,
null
)
)
.then(() => {
setSelectedDevice({});
setOpenRevoke(false);
})
.catch((error) => {
console.log(error);
});
const url =
api_path_delete_admin_device_revoke +
"?username=" +
username +
"&deviceId=" +
selectedDevice.deviceId;
requestConfig = configApiCall(url, "DELETE", null, null);
} else {
axios(
configApiCall(
api_path_delete_auth_device_revoke + "/" + selectedDevice.deviceId,
"DELETE",
null,
null
)
)
.then(() => {
setSelectedDevice({});
setOpenRevoke(false);
})
.catch((error) => {
console.log(error);
});
requestConfig = configApiCall(
api_path_delete_auth_device_revoke + "/" + selectedDevice.deviceId,
"DELETE",
null,
null
);
}
};
/**
* Formik Validation
*/
const validateDisplayName = (displaynamevalue) => {
let error;
if (!displaynamevalue) {
error = "Required";
}
return error;
axios(requestConfig)
.then(() => {
setSelectedDevice({});
setOpenRevoke(false);
})
.catch((error) => {
console.log(error);
});
};
const tableCellClasses = classnames(classes.tableCell);
......@@ -257,7 +190,7 @@ export default function Devices({ username }) {
{device.displayName}
</TableCell>
<TableCell className={tableCellClasses}>
{getDeviceId(device)}
{device.deviceId}
</TableCell>
<TableCell align="right" className={tableCellClasses}>
{getDeviceStatus(device)}
......@@ -309,88 +242,20 @@ export default function Devices({ username }) {
</TableBody>
)}
</Table>
<Dialog
open={openEdit}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-edit-dialog-title">
{i18next.t("update_device_information", "Update Device Information")}
</DialogTitle>
<Formik
initialValues={{
displayName: selectedDevice.displayName,
}}
>
{({ errors, touched, validateField }) => (
<form>
<DialogContent>
<Field name="displayName" validate={validateDisplayName}>
{({ field }) => (
<div>
<TextField
autoFocus
margin="dense"
id="name"
label={i18next.t(
"device_display_name",
"Device Display Name"
)}
fullWidth
onChange={setDisplayName(field.value)}
helperText={
errors.displayName &&
touched.displayName &&
errors.displayName
}
{...field}
/>
</div>
)}
</Field>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="primary">
{i18next.t("cancel", "Cancel")}
</Button>
<Button
onClick={() =>
validateField("displayName").then(() => handleUpdate())
}
color="primary"
>
{i18next.t("update", "Update")}
</Button>
</DialogActions>
</form>
)}
</Formik>
</Dialog>
<Dialog
open={openRevoke}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-revoke-dialog-title">
{i18next.t("revoke_device", "Revoke Device")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{i18next.t(
"are_you_sure_you_want_to_revoke_this_device",
"Are you sure you want to revoke this device?"
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="primary">
{i18next.t("cancel", "Cancel")}
</Button>
<Button onClick={handleDeviceRevoke} color="primary">
{i18next.t("confirm_revoke", "Confirm Revoke")}
</Button>
</DialogActions>
</Dialog>
<EditDeviceDialog
openEdit={openEdit}
handleClose={handleClose}
selectedDevice={selectedDevice}
setDisplayName={setDisplayName}
handleUpdate={handleUpdate}
/>
<RevokeDeviceDialog
openRevoke={openRevoke}
handleClose={handleClose}
handleDeviceRevoke={handleDeviceRevoke}
/>
</div>
);
}
};
export default Devices;
import {
Dialog,
DialogTitle,
DialogContent,
TextField,
DialogActions,
Button,
} from "@mui/material";
import { Formik, Field } from "formik";
import i18next from "i18next";
import React from "react";
import { Dispatch, FC, SetStateAction } from "react";
import { Device } from "./Devices";
interface EditDeviceDialogProps {
openEdit: boolean;
handleClose: () => void;
selectedDevice: Device;
setDisplayName: Dispatch<SetStateAction<string>>;
handleUpdate: () => void;
}
const EditDeviceDialog: FC<EditDeviceDialogProps> = ({
openEdit,
handleClose,
selectedDevice,
setDisplayName,
handleUpdate,
}) => {
/**
* Formik Validation
*/
const validateDisplayName = (displaynamevalue) => {
let error;
if (!displaynamevalue) {
error = "Required";
}
return error;
};
return (
<Dialog
open={openEdit}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-edit-dialog-title">
{i18next.t("update_device_information", "Update Device Information")}
</DialogTitle>
<Formik
initialValues={{
displayName: selectedDevice.displayName,
}}
>
{({ errors, touched, validateField }) => (
<form>
<DialogContent>
<Field name="displayName" validate={validateDisplayName}>
{({ field }) => (
<div>
<TextField
autoFocus
margin="dense"
id="name"
label={i18next.t(
"device_display_name",
"Device Display Name"
)}
fullWidth
onChange={setDisplayName(field.value)}
helperText={
errors.displayName &&
touched.displayName &&
errors.displayName
}
{...field}
/>
</div>
)}
</Field>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
{i18next.t("cancel", "Cancel")}
</Button>
<Button
onClick={() =>
validateField("displayName").then(() => handleUpdate())
}
color="primary"
>
{i18next.t("update", "Update")}
</Button>
</DialogActions>
</form>
)}
</Formik>
</Dialog>
);
};
export default EditDeviceDialog;
import React, { FC } from "react";
import i18next from "i18next";
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Button,
} from "@mui/material";
interface RevokeDeviceDialogProps {
openRevoke: boolean;
handleClose: () => void;
handleDeviceRevoke: () => void;
}
const RevokeDeviceDialog: FC<RevokeDeviceDialogProps> = ({
openRevoke,
handleClose,
handleDeviceRevoke,
}) => {
return (
<Dialog
open={openRevoke}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-revoke-dialog-title">
{i18next.t("revoke_device", "Revoke Device")}
</DialogTitle>
<DialogContent>
<DialogContentText>
{i18next.t(
"are_you_sure_you_want_to_revoke_this_device",
"Are you sure you want to revoke this device?"
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
{i18next.t("cancel", "Cancel")}
</Button>
<Button onClick={handleDeviceRevoke} color="primary">
{i18next.t("confirm_revoke", "Confirm Revoke")}
</Button>
</DialogActions>
</Dialog>
);
};
export default RevokeDeviceDialog;
import React, { FC, useState } from "react";
import { Link, useHistory } from "react-router-dom";
import classnames from "classnames";
import i18next from "i18next";
import {
Table,
TableHead,
TableRow,
TableCell,
TableBody,
} from "@mui/material";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import auth from "auth";
import GridItem from "components/Grid/GridItem";
import Button from "components/CustomButtons/Button";
import { Group, GroupMembership } from "./DisplayUserProfile";
import TemporaryDrawer from "components/Drawer/Drawer";
import configApiCall from "api";
import axios from "axios";
import {
api_path_get_list_group,
api_path_post_group_member,
} from "globalUrls";
interface AdminAddUserToGroupProps {
username: string;
openDrawer: boolean;
setOpenDrawer: (open: boolean) => void;
classes: any;
groupMemberships: GroupMembership[];
setGroupMemberships: (groupMemberships: GroupMembership[]) => void;
removeUserFromGroup: (group: GroupMembership) => void;
}
const AdminAddUserToGroup: FC<AdminAddUserToGroupProps> = ({
username,
openDrawer,
setOpenDrawer,
classes,
groupMemberships,
setGroupMemberships,
removeUserFromGroup,
}) => {
if (!auth.hasAdminScope()) {
return null;
}
const history = useHistory();
const [groups, setGroups] = useState<Group[]>([]);
const [searchValue, setSearchValue] = useState("");
const tableCellClasses = classnames(classes.tableCell);
const getAllGroups = (value: string) => {
if (value === "") value = "*";
axios(
configApiCall(
api_path_get_list_group + "?groupName=" + value,
"GET",
null,
null
)
)
.then((response) => {
const groupResults: Group[] = response.data;
setGroups(groupResults);
})
.catch((error) => {
if (error.response && error.response.status === 401) {
auth.authenticated = false;
history.push("/");
} else {
setGroups([]);
}
});
};
const addUserToGroup = (group: Group) => {
axios(
configApiCall(
api_path_post_group_member + group.id,
"POST",
{ username },
null
)
)
.then(() => {
let newGroupMemberships = groupMemberships;
newGroupMemberships.push({
groupId: group.id,
name: group.name,
});
setGroupMemberships(newGroupMemberships);
})
.catch((error) => {
console.log(error);
});
};
return (
<>
<TemporaryDrawer
openDrawer={openDrawer}
setOpenDrawer={setOpenDrawer}
direction="right"
placeholder={i18next.t("add_user_to_group", "Add user to group ...")}
searchTargets={setSearchValue}
targets={groups.filter((group) => group.name.includes(searchValue))}
existingTargets={groupMemberships}
addElementToTarget={addUserToGroup}
type="group"
/>
<GridItem xs={12} sm={12} md={12}>
<Button
color="primary"
onClick={() => {
setOpenDrawer(true);
getAllGroups("*");
}}
>
<AddCircleOutlineIcon />{" "}
{i18next.t("add_user_to_a_group", "Add user to a group")}
</Button>
<Table className={classes.table}>
<TableHead>
<TableRow>
<TableCell align="left">
{i18next.t("group_name", "Group name")}
</TableCell>
<TableCell align="right">
{i18next.t("action", "Action")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{groupMemberships.map((group) => (
<TableRow
key={group.groupId + group.name}
className={classes.tableRow}
>
<TableCell className={tableCellClasses}>
<Link to={`/group/${group.groupId}`}>{group.name}</Link>
</TableCell>
<TableCell align="right" className={classes.tableActions}>
<Button
color="primary"
onClick={() => removeUserFromGroup(group)}
>
{i18next.t("remove_from_group", "Remove from group")}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</GridItem>
</>
);
};
export default AdminAddUserToGroup;
import React, { FC } from "react";
import {
Grid,
List,
ListItem,
ListItemAvatar,
Avatar,
ListItemText,
} from "@mui/material";
import BusinessCenterOutlinedIcon from "@mui/icons-material/BusinessCenterOutlined";
import AlternateEmailOutlinedIcon from "@mui/icons-material/AlternateEmailOutlined";
import PhoneInTalkOutlinedIcon from "@mui/icons-material/PhoneInTalkOutlined";
import SmartphoneOutlinedIcon from "@mui/icons-material/SmartphoneOutlined";
import LocalPrintshopOutlinedIcon from "@mui/icons-material/LocalPrintshopOutlined";
import PhoneForwardedOutlinedIcon from "@mui/icons-material/PhoneForwardedOutlined";
import PersonIcon from "@mui/icons-material/Person";
import { UserProfile } from "./DisplayUserProfile";
interface UserProfileFieldsListProps {
user: UserProfile;
}
const UserProfileFieldsList: FC<UserProfileFieldsListProps> = ({ user }) => {
return (
<Grid item xs={12} sm={12} md={6}>
<List dense={false}>
{(user.firstName || user.lastName) && (
<ListItem>
<ListItemAvatar>
<Avatar>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={`${user.firstName} ${user.lastName}`} />
</ListItem>
)}
{user.phoneNumber && (
<ListItem>
<ListItemAvatar>
<Avatar>
<PhoneInTalkOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.phoneNumber} />
</ListItem>
)}
{user.organization && (
<ListItem>
<ListItemAvatar>
<Avatar>
<BusinessCenterOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.organization} />
</ListItem>
)}
</List>
<List dense={false}>
{user.email && (
<ListItem>
<ListItemAvatar>
<Avatar>
<AlternateEmailOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.email} />
</ListItem>
)}
{user.phoneNumberExtension && (
<ListItem>
<ListItemAvatar>
<Avatar>
<PhoneForwardedOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.phoneNumberExtension} />
</ListItem>
)}
{user.mobileNumber && (
<ListItem>
<ListItemAvatar>
<Avatar>
<SmartphoneOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.mobileNumber} />
</ListItem>
)}
{user.faxNumber && (
<ListItem>
<ListItemAvatar>
<Avatar>
<LocalPrintshopOutlinedIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.faxNumber} />
</ListItem>
)}
</List>
</Grid>
);
};
export default UserProfileFieldsList;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment