From e96c37483bd2ddd45e66d6b418525258f294deb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Banno-Cloutier?= <leo.banno-cloutier@savoirfairelinux.com> Date: Fri, 25 Aug 2023 14:19:02 -0400 Subject: [PATCH] jams-react-client: major refactor Change-Id: I1458f6ce4ae8deac3e310faa1c462b0145ef7fb9 --- jams-react-client/src/api.tsx | 27 ++++---- .../src/components/CaSetup/CaSetup.tsx | 2 +- .../src/components/CustomButtons/Button.tsx | 49 +++++--------- .../CustomUiPreview/CustomUiPreview.tsx | 2 +- .../src/components/Devices/Devices.tsx | 24 +++++-- .../components/Devices/EditDeviceDialog.tsx | 27 +++----- .../src/components/Drawer/Drawer.tsx | 2 +- .../src/components/Footer/Footer.tsx | 2 +- .../components/FormikField/FormikField.tsx | 65 ++++++++----------- .../src/components/Grid/GridContainer.tsx | 15 +++-- .../src/components/Grid/GridItem.tsx | 5 +- .../LanguagePicker/LanguagePicker.tsx | 15 +++-- .../src/components/Navbars/Navbar.tsx | 6 +- .../PasswordDialog/PasswordDialog.tsx | 2 +- .../src/components/Sidebar/Sidebar.tsx | 34 ++++++---- .../components/Snackbar/BlueprintSnackbar.tsx | 23 +++++-- jams-react-client/src/i18n.tsx | 3 - jams-react-client/src/layouts/BaseLayout.tsx | 19 ++++-- jams-react-client/src/layouts/ListLayout.tsx | 9 +-- jams-react-client/src/layouts/SignIn.tsx | 2 +- jams-react-client/src/layouts/SignUp.tsx | 2 +- .../src/views/Blueprint/ColorPickerPopup.tsx | 18 ++++- .../Blueprint/EditBlueprintPermissions.tsx | 2 +- .../src/views/Blueprint/EditBlueprintUi.tsx | 9 ++- .../src/views/Blueprint/PolicyDataContext.tsx | 4 +- .../src/views/Blueprint/parsePolicyData.tsx | 8 ++- .../src/views/Blueprint/updatePolicyData.tsx | 2 +- .../src/views/Contacts/Contacts.tsx | 7 +- .../src/views/Groups/EditGroup.tsx | 9 +-- .../src/views/Settings/General.tsx | 5 +- .../views/UserProfile/DisplayUserProfile.tsx | 34 ++++++++-- .../UserProfile/EditCreateUserProfile.tsx | 54 +++++++++------ .../src/views/UserProfile/cropImage.tsx | 4 +- jams-react-client/src/views/Users/Users.tsx | 2 +- 34 files changed, 278 insertions(+), 215 deletions(-) diff --git a/jams-react-client/src/api.tsx b/jams-react-client/src/api.tsx index 1dfb7b79..8ee52e8b 100644 --- a/jams-react-client/src/api.tsx +++ b/jams-react-client/src/api.tsx @@ -18,6 +18,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { AxiosRequestConfig } from "axios"; import { url_path, url_port, @@ -31,18 +32,16 @@ import { api_path_blueprints, } from "./globalUrls"; -export default function configApiCall( - api_path, - request_type, - data, - credentials -) { +const configApiCall = ( + api_path: string, + request_type: string, + data: any, + credentials: null +): AxiosRequestConfig => { // build config call - const config = { + const config: AxiosRequestConfig = { url: url_path + ":" + url_port + api_path, method: request_type, - crossDomain: true, - dataType: "json", headers: {}, }; @@ -51,15 +50,15 @@ export default function configApiCall( const base64 = btoa( credentials["username"] + ":" + credentials["password"] ); - config["headers"]["Authorization"] = "Basic " + base64; + config.headers!["Authorization"] = "Basic " + base64; } const jwt = localStorage.getItem("access_token"); if (jwt) { - config["headers"]["Authorization"] = "Bearer " + jwt; + config.headers!["Authorization"] = "Bearer " + jwt; } - config["headers"]["Content-type"] = "application/json;charset=UTF-8"; + config.headers!["Content-type"] = "application/json;charset=UTF-8"; // pass data in the header if (data) { @@ -84,4 +83,6 @@ export default function configApiCall( } return config; -} +}; + +export default configApiCall; diff --git a/jams-react-client/src/components/CaSetup/CaSetup.tsx b/jams-react-client/src/components/CaSetup/CaSetup.tsx index e9d35137..d0d7d2f7 100644 --- a/jams-react-client/src/components/CaSetup/CaSetup.tsx +++ b/jams-react-client/src/components/CaSetup/CaSetup.tsx @@ -246,7 +246,7 @@ export default function CaSetup(props) { } /> - <CountrySelect defaultValue={values.country} {...props} /> + <CountrySelect {...props} /> {touched.country && errors.country ? ( <span className="spanError">{errors.country}</span> ) : null} diff --git a/jams-react-client/src/components/CustomButtons/Button.tsx b/jams-react-client/src/components/CustomButtons/Button.tsx index 3b4aeaf5..f695c959 100644 --- a/jams-react-client/src/components/CustomButtons/Button.tsx +++ b/jams-react-client/src/components/CustomButtons/Button.tsx @@ -1,16 +1,26 @@ +import { FC, ReactNode } from "react"; import classNames from "classnames"; -// nodejs library to set properties for components -import PropTypes from "prop-types"; -// material-ui components +import Button, { ButtonProps } from "@mui/material/Button"; import { makeStyles } from "@mui/styles"; -import Button from "@mui/material/Button"; import styles from "assets/jss/material-dashboard-react/components/buttonStyle"; const useStyles = makeStyles(styles as any); -export default function RegularButton(props) { +interface RegularButtonProps extends ButtonProps { + size?: "small" | "large"; + simple?: boolean; + round?: boolean; + disabled?: boolean; + block?: boolean; + link?: boolean; + justIcon?: boolean; + className?: string; + children?: ReactNode; +} + +const RegularButton: FC<RegularButtonProps> = (props) => { const classes = useStyles(); const { color, @@ -23,7 +33,6 @@ export default function RegularButton(props) { link, justIcon, className, - muiClasses, ...rest } = props; const btnClasses = classNames({ @@ -39,32 +48,10 @@ export default function RegularButton(props) { [className]: className, }); return ( - <Button {...rest} classes={muiClasses} className={btnClasses}> + <Button {...rest} className={btnClasses}> {children} </Button> ); -} - -RegularButton.propTypes = { - color: PropTypes.oneOf([ - "primary", - "info", - "success", - "warning", - "danger", - "rose", - "white", - "transparent", - ]), - size: PropTypes.oneOf(["small", "large"]), - simple: PropTypes.bool, - round: PropTypes.bool, - disabled: PropTypes.bool, - block: PropTypes.bool, - link: PropTypes.bool, - justIcon: PropTypes.bool, - className: PropTypes.string, - // use this to pass the classes props from Material-UI - muiClasses: PropTypes.object, - children: PropTypes.node, }; + +export default RegularButton; diff --git a/jams-react-client/src/components/CustomUiPreview/CustomUiPreview.tsx b/jams-react-client/src/components/CustomUiPreview/CustomUiPreview.tsx index 65a0cacc..753c214a 100644 --- a/jams-react-client/src/components/CustomUiPreview/CustomUiPreview.tsx +++ b/jams-react-client/src/components/CustomUiPreview/CustomUiPreview.tsx @@ -103,7 +103,7 @@ const styles = { const useStyles = makeStyles(styles as any); interface CustomUiPreviewProps { - isOldPreview: boolean; + isOldPreview?: boolean; opacity: number; uiCustomization: UiCustomization; } diff --git a/jams-react-client/src/components/Devices/Devices.tsx b/jams-react-client/src/components/Devices/Devices.tsx index 06c36eaf..7d9dd9c6 100755 --- a/jams-react-client/src/components/Devices/Devices.tsx +++ b/jams-react-client/src/components/Devices/Devices.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; import classnames from "classnames"; @@ -49,7 +49,11 @@ const Devices: FC<DevicesProps> = ({ username }) => { const history = useHistory(); const [devices, setDevices] = useState<Device[]>([]); - const [selectedDevice, setSelectedDevice] = useState<Device>({}); + const [selectedDevice, setSelectedDevice] = useState<Device>({ + deviceId: "", + displayName: "", + revoked: false, + }); const [displayName, setDisplayName] = useState(""); const [openEdit, setOpenEdit] = useState(false); @@ -83,13 +87,19 @@ const Devices: FC<DevicesProps> = ({ username }) => { return device.revoked ? "Revoked" : "Active"; }; - const handleClickEdit = (e: MouseEvent, device: Device) => { + const handleClickEdit = ( + e: React.MouseEvent<HTMLButtonElement, MouseEvent>, + device: Device + ) => { e.preventDefault(); setOpenEdit(true); setSelectedDevice(device); }; - const handleClickRevoke = (e: MouseEvent, device: Device) => { + const handleClickRevoke = ( + e: React.MouseEvent<HTMLButtonElement, MouseEvent>, + device: Device + ) => { e.preventDefault(); setOpenRevoke(true); setSelectedDevice(device); @@ -98,7 +108,7 @@ const Devices: FC<DevicesProps> = ({ username }) => { const handleClose = () => { setOpenEdit(false); setOpenRevoke(false); - setSelectedDevice({}); + setSelectedDevice(undefined); }; const handleUpdate = () => { @@ -131,7 +141,7 @@ const Devices: FC<DevicesProps> = ({ username }) => { axios(requestConfig) .then(() => { - setSelectedDevice({}); + setSelectedDevice(undefined); setOpenEdit(false); }) .catch((error) => { @@ -160,7 +170,7 @@ const Devices: FC<DevicesProps> = ({ username }) => { axios(requestConfig) .then(() => { - setSelectedDevice({}); + setSelectedDevice(undefined); setOpenRevoke(false); }) .catch((error) => { diff --git a/jams-react-client/src/components/Devices/EditDeviceDialog.tsx b/jams-react-client/src/components/Devices/EditDeviceDialog.tsx index cc7fac87..b8d9c5a7 100644 --- a/jams-react-client/src/components/Devices/EditDeviceDialog.tsx +++ b/jams-react-client/src/components/Devices/EditDeviceDialog.tsx @@ -9,6 +9,7 @@ import { import { Formik, Field } from "formik"; import i18next from "i18next"; import { Dispatch, FC, SetStateAction } from "react"; +import * as Yup from "yup"; import { Device } from "./Devices"; interface EditDeviceDialogProps { @@ -26,16 +27,9 @@ const EditDeviceDialog: FC<EditDeviceDialogProps> = ({ setDisplayName, handleUpdate, }) => { - /** - * Formik Validation - */ - const validateDisplayName = (displaynamevalue) => { - let error; - if (!displaynamevalue) { - error = "Required"; - } - return error; - }; + const validationSchema = Yup.object({ + displayName: Yup.string().required("Required"), + }); return ( <Dialog @@ -55,11 +49,13 @@ const EditDeviceDialog: FC<EditDeviceDialogProps> = ({ initialValues={{ displayName: selectedDevice.displayName, }} + onSubmit={handleUpdate} + validationSchema={validationSchema} > - {({ errors, touched, validateField }) => ( + {({ errors, touched }) => ( <form> <DialogContent> - <Field name="displayName" validate={validateDisplayName}> + <Field name="displayName"> {({ field }) => ( <div> <TextField @@ -87,12 +83,7 @@ const EditDeviceDialog: FC<EditDeviceDialogProps> = ({ <Button onClick={handleClose} color="primary"> {i18next.t("cancel", "Cancel") as string} </Button> - <Button - onClick={() => - validateField("displayName").then(() => handleUpdate()) - } - color="primary" - > + <Button type="submit" color="primary"> {i18next.t("update", "Update") as string} </Button> </DialogActions> diff --git a/jams-react-client/src/components/Drawer/Drawer.tsx b/jams-react-client/src/components/Drawer/Drawer.tsx index 4cfb0bea..e1db33d8 100644 --- a/jams-react-client/src/components/Drawer/Drawer.tsx +++ b/jams-react-client/src/components/Drawer/Drawer.tsx @@ -144,7 +144,7 @@ const TemporaryDrawer: FC<TemporaryDrawerProps> = ({ })} role="presentation" > - <div className={classes.searchWrapper}> + <div> <CustomInput formControlProps={{ className: classes.margin + " " + classes.search, diff --git a/jams-react-client/src/components/Footer/Footer.tsx b/jams-react-client/src/components/Footer/Footer.tsx index a8bddce5..32d790b0 100755 --- a/jams-react-client/src/components/Footer/Footer.tsx +++ b/jams-react-client/src/components/Footer/Footer.tsx @@ -14,7 +14,7 @@ export default function Footer() { <p className={classes.right}> <span> JAMS Version {pjson.version.slice(0, 3)} - © - {1900 + new Date().getYear()}{" "} + {new Date().getFullYear()}{" "} <a href="https://savoirfairelinux.com" target="_blank" diff --git a/jams-react-client/src/components/FormikField/FormikField.tsx b/jams-react-client/src/components/FormikField/FormikField.tsx index 0f6df7aa..3a4c2e03 100644 --- a/jams-react-client/src/components/FormikField/FormikField.tsx +++ b/jams-react-client/src/components/FormikField/FormikField.tsx @@ -6,45 +6,34 @@ import FormControl from "@mui/material/FormControl"; import InputLabel from "@mui/material/InputLabel"; import Input from "@mui/material/Input"; -class FormikField extends Component { - render() { - return ( - <div className="FormikField"> - <FormControl - size="medium" - error={ - this.props.onKeyUpError || - <ErrorMessage name={this.props.name} /> === "" - } +const FormikField = (props) => { + return ( + <div className="FormikField"> + <FormControl size="medium" error={props.onKeyUpError} fullWidth> + <InputLabel htmlFor={props.name}> + {props.onKeyUpError ? ( + props.onKeyUpErrorMessage + ) : ( + <ErrorMessage name={props.name} /> + )} + </InputLabel> + <Field + placeholder={props.placeholder} + required={props.required} + name={props.name} + as={Input} + startAdornment={props.startAdornment} + endAdornment={props.endAdornment} + label={props.label} fullWidth - > - <InputLabel htmlFor={this.props.name}> - {this.props.onKeyUpError ? ( - this.props.onKeyUpErrorMessage - ) : "" || <ErrorMessage name={this.props.name} /> === "" ? ( - this.props.label - ) : ( - <ErrorMessage name={this.props.name} /> - )} - </InputLabel> - <Field - placeholder={this.props.placeholder} - required={this.props.required} - name={this.props.name} - as={Input} - startAdornment={this.props.startAdornment} - endAdornment={this.props.endAdornment} - label={this.props.label} - fullWidth - type={this.props.type} - autoComplete={this.props.autoComplete} - onKeyUp={(e) => this.props.handleChange(e.target.value)} - /> - </FormControl> - </div> - ); - } -} + type={props.type} + autoComplete={props.autoComplete} + onKeyUp={(e) => props.handleChange(e.target.value)} + /> + </FormControl> + </div> + ); +}; FormikField.propTypes = { startAdornment: PropTypes.element, diff --git a/jams-react-client/src/components/Grid/GridContainer.tsx b/jams-react-client/src/components/Grid/GridContainer.tsx index 8c3e9718..012a39c6 100644 --- a/jams-react-client/src/components/Grid/GridContainer.tsx +++ b/jams-react-client/src/components/Grid/GridContainer.tsx @@ -1,7 +1,8 @@ import PropTypes from "prop-types"; // @mui/material components import { makeStyles } from "@mui/styles"; -import Grid from "@mui/material/Grid"; +import Grid, { GridProps } from "@mui/material/Grid"; +import { FC, ReactNode } from "react"; const styles = { grid: { @@ -12,7 +13,11 @@ const styles = { const useStyles = makeStyles(styles as any); -export default function GridContainer(props) { +interface GridContainerProps extends GridProps { + children: ReactNode; +} + +const GridContainer: FC<GridContainerProps> = (props) => { const classes = useStyles(); const { children, ...rest } = props; return ( @@ -20,8 +25,6 @@ export default function GridContainer(props) { {children} </Grid> ); -} - -GridContainer.propTypes = { - children: PropTypes.node, }; + +export default GridContainer; diff --git a/jams-react-client/src/components/Grid/GridItem.tsx b/jams-react-client/src/components/Grid/GridItem.tsx index 1dc6ffb4..eb69a955 100644 --- a/jams-react-client/src/components/Grid/GridItem.tsx +++ b/jams-react-client/src/components/Grid/GridItem.tsx @@ -1,8 +1,7 @@ import { FC } from "react"; import * as React from "react"; import { makeStyles } from "@mui/styles"; -import { Grid, GridTypeMap } from "@mui/material"; -import { OverridableComponent } from "@mui/material/OverridableComponent"; +import { Grid, GridProps } from "@mui/material"; const styles = { grid: { @@ -12,7 +11,7 @@ const styles = { const useStyles = makeStyles(styles as any); -interface GridItemProps extends OverridableComponent<GridTypeMap> { +interface GridItemProps extends GridProps { children: React.ReactNode; } diff --git a/jams-react-client/src/components/LanguagePicker/LanguagePicker.tsx b/jams-react-client/src/components/LanguagePicker/LanguagePicker.tsx index 38a4619d..b485ce1e 100644 --- a/jams-react-client/src/components/LanguagePicker/LanguagePicker.tsx +++ b/jams-react-client/src/components/LanguagePicker/LanguagePicker.tsx @@ -10,12 +10,17 @@ import i18next from "i18next"; import { useTranslation } from "react-i18next"; +interface Language { + code: string; + name: string; +} + export default function LanguagePicker(props) { const history = useHistory(); - const [language, setLanguage] = useState( + const [language, setLanguage] = useState<string>( i18next.language || window.localStorage.i18nextLng || "en" ); - const [languages, setLanguages] = useState([]); + const [languages, setLanguages] = useState<Language[]>([]); const { i18n } = useTranslation(); @@ -30,8 +35,10 @@ export default function LanguagePicker(props) { fetch("/available_languages.json") .then((res) => res.json()) .then((result) => { - const response = result.data.map(({ attributes }) => attributes); - let translates_languages = [ + const response: Language[] = result.data.map( + ({ attributes }: { attributes: Language }) => attributes + ); + const translates_languages: Language[] = [ { code: "en", name: "English" }, ...response, ]; diff --git a/jams-react-client/src/components/Navbars/Navbar.tsx b/jams-react-client/src/components/Navbars/Navbar.tsx index b41457ff..273389c7 100755 --- a/jams-react-client/src/components/Navbars/Navbar.tsx +++ b/jams-react-client/src/components/Navbars/Navbar.tsx @@ -25,11 +25,7 @@ export default function Header(props) { <AppBar className={classes.appBar + appBarClasses}> <Toolbar className={classes.container}> <div className={classes.flex}> - <Button - color="transparent" - href="#" - className={classes.title} - ></Button> + <Button color="info" className={classes.title}></Button> </div> <Hidden mdUp implementation="css"> <IconButton diff --git a/jams-react-client/src/components/PasswordDialog/PasswordDialog.tsx b/jams-react-client/src/components/PasswordDialog/PasswordDialog.tsx index 2b4e4fda..effc5b09 100644 --- a/jams-react-client/src/components/PasswordDialog/PasswordDialog.tsx +++ b/jams-react-client/src/components/PasswordDialog/PasswordDialog.tsx @@ -271,7 +271,7 @@ const PasswordDialog: FC<PasswordDialogProps> = ({ </Button> <Button type="submit" - disable={!isValid && !dirty} + disabled={!isValid && !dirty} color="info" className={classes.whiteButtonText} autoFocus diff --git a/jams-react-client/src/components/Sidebar/Sidebar.tsx b/jams-react-client/src/components/Sidebar/Sidebar.tsx index 3a9fbc1d..2acc3c87 100755 --- a/jams-react-client/src/components/Sidebar/Sidebar.tsx +++ b/jams-react-client/src/components/Sidebar/Sidebar.tsx @@ -1,4 +1,4 @@ -import { createRef, useEffect, useState } from "react"; +import { FC, createRef, useEffect, useState } from "react"; import { Link, useHistory } from "react-router-dom"; import classNames from "classnames"; import PropTypes from "prop-types"; @@ -21,10 +21,25 @@ import styles from "assets/jss/material-dashboard-react/components/sidebarStyle" import auth from "auth"; import i18next from "i18next"; +import { Route } from "layouts/BaseLayout"; const useStyles = makeStyles(styles as any); -export default function Sidebar(props) { +interface SidebarProps { + updating: boolean; + snackbarMessage: string; + rtlActive: boolean; + handleQuery: () => void; + setOpenUpdate: (open: boolean) => void; + open: boolean; + handleDrawerToggle: () => void; + color: string; + logo: string; + image: string; + routes: Route[]; +} + +const Sidebar: FC<SidebarProps> = (props) => { const classes = useStyles(); const mainPanel = createRef(); const [open, setOpen] = useState(false); @@ -99,7 +114,7 @@ export default function Sidebar(props) { /> )} <ListItemText - primary={props.rtlActive ? prop.rtlName : prop.name} + primary={prop.name} className={classNames(classes.itemText, whiteFontClasses, { [classes.itemTextRTL]: props.rtlActive, })} @@ -216,15 +231,6 @@ export default function Sidebar(props) { </Hidden> </div> ); -} - -Sidebar.propTypes = { - rtlActive: PropTypes.bool, - handleDrawerToggle: PropTypes.func, - bgColor: PropTypes.oneOf(["purple", "blue", "green", "orange", "red"]), - logo: PropTypes.string, - image: PropTypes.string, - logoText: PropTypes.string, - routes: PropTypes.arrayOf(PropTypes.object), - open: PropTypes.bool, }; + +export default Sidebar; diff --git a/jams-react-client/src/components/Snackbar/BlueprintSnackbar.tsx b/jams-react-client/src/components/Snackbar/BlueprintSnackbar.tsx index 9c50167b..9b1a0fe7 100644 --- a/jams-react-client/src/components/Snackbar/BlueprintSnackbar.tsx +++ b/jams-react-client/src/components/Snackbar/BlueprintSnackbar.tsx @@ -1,16 +1,29 @@ -import { forwardRef, createRef, Dispatch, FC, SetStateAction } from "react"; +import { + forwardRef, + createRef, + Dispatch, + FC, + SetStateAction, + ForwardedRef, +} from "react"; import MuiAlert from "@mui/material/Alert"; import Snackbar from "@mui/material/Snackbar"; -import Slide from "@mui/material/Slide"; +import Slide, { SlideProps } from "@mui/material/Slide"; import { SnackbarProps } from "views/Blueprint/PolicyDataContext"; // https://stackoverflow.com/a/67961603 -const Alert = forwardRef(function Alert(props, ref) { +const Alert = forwardRef(function Alert( + props: any, + ref: ForwardedRef<HTMLDivElement> +) { return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />; }); -const SlideTransition = forwardRef(function SlideTransition(props, ref) { +const SlideTransition = forwardRef(function SlideTransition( + props: SlideProps, + ref +) { return <Slide ref={ref} {...props} direction="left" />; }); @@ -23,7 +36,7 @@ export const BlueprintSnackbar: FC<BlueprintSnackbarProps> = ({ snackbar, setSnackbar, }) => { - const snackbarRef = createRef(null); + const snackbarRef = createRef(); const handleClose = () => { setSnackbar((state) => ({ ...state, open: false })); diff --git a/jams-react-client/src/i18n.tsx b/jams-react-client/src/i18n.tsx index d251bfcb..72f43d2f 100644 --- a/jams-react-client/src/i18n.tsx +++ b/jams-react-client/src/i18n.tsx @@ -9,9 +9,6 @@ i18next .use(Backend) .init({ fallbackLng: "en", - lookupCookie: "i18next", - lookupLocalStorage: "i18nextLng", - lookupSessionStorage: "i18nextLng", debug: false, detection: { order: ["cookie", "localStorage", "sessionStorage", "navigator"], diff --git a/jams-react-client/src/layouts/BaseLayout.tsx b/jams-react-client/src/layouts/BaseLayout.tsx index 81620015..86483d29 100644 --- a/jams-react-client/src/layouts/BaseLayout.tsx +++ b/jams-react-client/src/layouts/BaseLayout.tsx @@ -1,4 +1,4 @@ -import { createRef, useEffect, useState } from "react"; +import { FC, ReactNode, createRef, useEffect, useState } from "react"; // creates a beautiful scrollbar import PerfectScrollbar from "perfect-scrollbar"; import "perfect-scrollbar/css/perfect-scrollbar.css"; @@ -46,15 +46,24 @@ let ps; const useStyles = makeStyles(styles as any); +export interface Route { + path: string; + name: string; + icon: FC<any>; + component: FC<any>; + layout: string; + admin?: boolean; +} + export default function Admin(props) { // styles const classes = useStyles(); // ref to help us initialize PerfectScrollbar on windows devices - const mainPanel = createRef(); + const mainPanel = createRef<HTMLDivElement>(); // states and functions const [mobileOpen, setMobileOpen] = useState(false); const [open, setOpen] = useState(false); - const [message, setMessage] = useState(false); + const [message, setMessage] = useState(""); const [openUpdate, setOpenUpdate] = useState(false); const [dialogMessage, setDialogMessage] = useState(""); const [messageYes, setMessageYes] = useState(""); @@ -63,7 +72,7 @@ export default function Admin(props) { const [query, setQuery] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); - const Routes = [ + const Routes: Route[] = [ { path: `/user/${auth.getUsername()}`, name: i18next.t("myprofile", "My profile") as string, @@ -237,7 +246,6 @@ export default function Admin(props) { </Dialog> <Sidebar routes={Routes} - logoText={"Jams"} logo={logo} image={bgImage} handleDrawerToggle={handleDrawerToggle} @@ -247,6 +255,7 @@ export default function Admin(props) { updating={updating} snackbarMessage={snackbarMessage} setOpenUpdate={setOpenUpdate} + rtlActive={false} /> <div className={classes.mainPanel} ref={mainPanel}> diff --git a/jams-react-client/src/layouts/ListLayout.tsx b/jams-react-client/src/layouts/ListLayout.tsx index 3ecd4c97..89552f2c 100644 --- a/jams-react-client/src/layouts/ListLayout.tsx +++ b/jams-react-client/src/layouts/ListLayout.tsx @@ -41,6 +41,7 @@ import DialogContentText from "@mui/material/DialogContentText/DialogContentText import Button from "@mui/material/Button"; import i18next from "i18next"; +import { Route } from "./BaseLayout"; let ps; @@ -50,11 +51,11 @@ export default function Admin(props) { // styles const classes = useStyles(); // ref to help us initialize PerfectScrollbar on windows devices - const mainPanel = createRef(); + const mainPanel = createRef<HTMLDivElement>(); // states and functions const [mobileOpen, setMobileOpen] = useState(false); const [open, setOpen] = useState(false); - const [message, setMessage] = useState(false); + const [message, setMessage] = useState(""); const [openUpdate, setOpenUpdate] = useState(false); const [dialogMessage, setDialogMessage] = useState(""); const [messageYes, setMessageYes] = useState(""); @@ -63,7 +64,7 @@ export default function Admin(props) { const [query, setQuery] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); - const Routes = [ + const Routes: Route[] = [ { path: `/user/${auth.getUsername()}`, name: i18next.t("myprofile", "My profile") as string, @@ -239,7 +240,6 @@ export default function Admin(props) { </Dialog> <Sidebar routes={Routes} - logoText={"Jams"} logo={logo} image={bgImage} handleDrawerToggle={handleDrawerToggle} @@ -249,6 +249,7 @@ export default function Admin(props) { updating={updating} snackbarMessage={snackbarMessage} setOpenUpdate={setOpenUpdate} + rtlActive={false} /> <div className={classes.mainPanel} ref={mainPanel}> diff --git a/jams-react-client/src/layouts/SignIn.tsx b/jams-react-client/src/layouts/SignIn.tsx index c08118b3..1a5f8b8c 100644 --- a/jams-react-client/src/layouts/SignIn.tsx +++ b/jams-react-client/src/layouts/SignIn.tsx @@ -90,7 +90,7 @@ export default function SignIn() { password: values.password, }; auth.login(jsonData, () => { - if (auth.authenticated && auth.access_token !== "") { + if (auth.authenticated) { auth.checkLastKnownStep(() => { auth.checkDirectoryType(() => { if (auth.isServerInstalled()) { diff --git a/jams-react-client/src/layouts/SignUp.tsx b/jams-react-client/src/layouts/SignUp.tsx index fdbd27e4..182ee85d 100644 --- a/jams-react-client/src/layouts/SignUp.tsx +++ b/jams-react-client/src/layouts/SignUp.tsx @@ -51,7 +51,7 @@ const useStyles = makeStyles((theme: Theme) => ({ marginTop: theme.spacing(3), marginBottom: theme.spacing(3), padding: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { + [theme.breakpoints.up(700)]: { marginTop: theme.spacing(6), marginBottom: theme.spacing(6), padding: theme.spacing(3), diff --git a/jams-react-client/src/views/Blueprint/ColorPickerPopup.tsx b/jams-react-client/src/views/Blueprint/ColorPickerPopup.tsx index c37b7063..3f316be5 100644 --- a/jams-react-client/src/views/Blueprint/ColorPickerPopup.tsx +++ b/jams-react-client/src/views/Blueprint/ColorPickerPopup.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { FC, useState } from "react"; import { makeStyles } from "@mui/styles"; import { HexAlphaColorPicker, HexColorPicker } from "react-colorful"; @@ -33,7 +33,17 @@ const styles = { const useStyles = makeStyles(styles as any); -export default function ColorPickerPopup({ hasAlphaChannel, color, onChange }) { +interface ColorPickerPopupProps { + hasAlphaChannel?: boolean; + color: string; + onChange: (color: string) => void; +} + +const ColorPickerPopup: FC<ColorPickerPopupProps> = ({ + hasAlphaChannel, + color, + onChange, +}) => { const classes = useStyles(); const [displayColorPicker, setDisplayColorPicker] = useState(false); @@ -69,4 +79,6 @@ export default function ColorPickerPopup({ hasAlphaChannel, color, onChange }) { )} </div> ); -} +}; + +export default ColorPickerPopup; diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.tsx b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.tsx index d1511c11..819e11c0 100644 --- a/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.tsx +++ b/jams-react-client/src/views/Blueprint/EditBlueprintPermissions.tsx @@ -120,7 +120,7 @@ export default function EditBlueprintPermissions(props) { allowLookup, } = policyData; - const searchUsers = (value) => { + const searchUsers = (value?: string) => { axios( configApiCall( api_path_get_user_directory_search, diff --git a/jams-react-client/src/views/Blueprint/EditBlueprintUi.tsx b/jams-react-client/src/views/Blueprint/EditBlueprintUi.tsx index c8368b22..f009d50c 100644 --- a/jams-react-client/src/views/Blueprint/EditBlueprintUi.tsx +++ b/jams-react-client/src/views/Blueprint/EditBlueprintUi.tsx @@ -22,7 +22,10 @@ import i18next from "i18next"; import { CustomUiPreview } from "components/CustomUiPreview/CustomUiPreview"; import EditBlueprintUiForm from "./EditBlueprintUiForm"; -import { DEFAULT_UI_CUSTOMIZATION } from "./policyData.constants"; +import { + DEFAULT_UI_CUSTOMIZATION, + UiCustomization, +} from "./policyData.constants"; import { PolicyDataContext } from "./PolicyDataContext"; const styles = { @@ -104,7 +107,7 @@ export default function EditBlueprintUi({ blueprintName }) { const [oldUiCustomization, setOldUiCustomization] = useState(uiCustomization); const [opacity, setOpacity] = useState(0); - const handleUpdateUi = (field: string, value: any) => { + const handleUpdateUi = (field: string | UiCustomization, value?: any) => { let newUiCustomization; if (typeof field === "object") { @@ -206,7 +209,7 @@ export default function EditBlueprintUi({ blueprintName }) { /> <CustomUiPreview isOldPreview - opacity="1" + opacity={1} uiCustomization={oldUiCustomization} /> </Grid> diff --git a/jams-react-client/src/views/Blueprint/PolicyDataContext.tsx b/jams-react-client/src/views/Blueprint/PolicyDataContext.tsx index 9fa77385..894bccca 100644 --- a/jams-react-client/src/views/Blueprint/PolicyDataContext.tsx +++ b/jams-react-client/src/views/Blueprint/PolicyDataContext.tsx @@ -16,7 +16,7 @@ export interface SnackbarProps { interface PolicyDataProp { policyData: PolicyData; - updatePolicyData: (field: string, value: string) => void; + updatePolicyData: (field: string, value: any) => void; snackbar: SnackbarProps; setSnackbar: (snackbar: SnackbarProps) => void; } @@ -61,7 +61,7 @@ export const PolicyDataContextProvider: FC<Props> = ({ }); }, [blueprintName]); - const updatePolicyData = (field: string, value: string) => { + const updatePolicyData = (field: string, value: any) => { _updatePolicyData( blueprintName, policyData, diff --git a/jams-react-client/src/views/Blueprint/parsePolicyData.tsx b/jams-react-client/src/views/Blueprint/parsePolicyData.tsx index dde06e90..8087581a 100644 --- a/jams-react-client/src/views/Blueprint/parsePolicyData.tsx +++ b/jams-react-client/src/views/Blueprint/parsePolicyData.tsx @@ -5,7 +5,11 @@ import { api_path_get_ns_name_from_addr, api_path_get_user_profile, } from "../../globalUrls"; -import { DEFAULT_UI_CUSTOMIZATION, PolicyData } from "./policyData.constants"; +import { + DEFAULT_UI_CUSTOMIZATION, + PolicyData, + UiCustomization, +} from "./policyData.constants"; const getModerators = async (defaultModerators: string) => { const moderators = []; @@ -78,7 +82,7 @@ const setCustomizationSettings = (policyData) => { } const result = JSON.parse(policyData.uiCustomization); - const ui = { isCustomizationEnabled: true }; + const ui: Partial<UiCustomization> = { isCustomizationEnabled: true }; ui.hasTitle = result.title !== ""; ui.title = result.title; diff --git a/jams-react-client/src/views/Blueprint/updatePolicyData.tsx b/jams-react-client/src/views/Blueprint/updatePolicyData.tsx index 8066a59d..c1ecc173 100644 --- a/jams-react-client/src/views/Blueprint/updatePolicyData.tsx +++ b/jams-react-client/src/views/Blueprint/updatePolicyData.tsx @@ -89,7 +89,7 @@ const updateUiCustomization = (data) => { logoUrl, logoSize, } = data.uiCustomization; - const ui = {}; + const ui: Partial<ServerUiCustomization> = {}; if (hasTitle === false) { ui.title = ""; diff --git a/jams-react-client/src/views/Contacts/Contacts.tsx b/jams-react-client/src/views/Contacts/Contacts.tsx index cfcaed83..af726a06 100644 --- a/jams-react-client/src/views/Contacts/Contacts.tsx +++ b/jams-react-client/src/views/Contacts/Contacts.tsx @@ -92,12 +92,12 @@ export default function Users(props) { const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [openDrawer, setOpenDrawer] = useState(false); - const [removedContact, setRemovedContact] = useState(); + const [removedContact, setRemovedContact] = useState(""); const [removedContactName, setRemovedContactName] = useState(); const [open, setOpen] = useState(false); const [allowedToAdd, setAllowedToAdd] = useState(true); - const searchContacts = (value) => { + const searchContacts = (value?: string) => { axios( configApiCall( api_path_get_user_directory_search, @@ -161,6 +161,7 @@ export default function Users(props) { axios( configApiCall( api_path_get_ns_name_from_addr + contact.uri, + "GET", null, null ) @@ -503,7 +504,7 @@ export default function Users(props) { )} </GridItem> ))} - {contacts === [] && + {contacts.length === 0 && ((props.username + i18next.t("has_no_contacts", " has no contacts")) as string)} </GridContainer> diff --git a/jams-react-client/src/views/Groups/EditGroup.tsx b/jams-react-client/src/views/Groups/EditGroup.tsx index ef28b951..9b0503a4 100644 --- a/jams-react-client/src/views/Groups/EditGroup.tsx +++ b/jams-react-client/src/views/Groups/EditGroup.tsx @@ -64,9 +64,10 @@ import auth from "auth"; import { debounce } from "lodash"; import { getBlueprintsOptions } from "./getBlueprintsOptions"; +import { ClassNameMap } from "@mui/material"; const useStyles = makeStyles(() => ({ - ...devicesStyle, + ...(devicesStyle as any), ...dashboardStyle, root: { flexGrow: 1, @@ -112,7 +113,7 @@ const useStyles = makeStyles(() => ({ })); export default function EditGroup(props) { - const classes = useStyles(); + const classes: ClassNameMap<any> = useStyles(); const history = useHistory(); const [name, setName] = useState(""); @@ -209,7 +210,7 @@ export default function EditGroup(props) { getBlueprintsOptions(blueprints) ); - const updateGroup = (blueprintValue) => { + const updateGroup = (blueprintValue?: string) => { const data = { name: newName, blueprint: blueprintValue ? blueprintValue : selectedBlueprint.label, @@ -231,7 +232,7 @@ export default function EditGroup(props) { }); }; - const searchUsers = (value) => { + const searchUsers = (value?: string) => { axios( configApiCall( api_path_get_user_directory_search, diff --git a/jams-react-client/src/views/Settings/General.tsx b/jams-react-client/src/views/Settings/General.tsx index 09561994..fba0f28a 100644 --- a/jams-react-client/src/views/Settings/General.tsx +++ b/jams-react-client/src/views/Settings/General.tsx @@ -207,7 +207,7 @@ export default function General(props) { onKeyUpErrorMessage="" /> {touched.password && errors.password ? ( - <span>{errors.password}</span> + <span>{errors.password.toString()}</span> ) : null} <FormikField @@ -245,7 +245,7 @@ export default function General(props) { onKeyUpErrorMessage="" /> {touched.confirmPassword && errors.confirmPassword ? ( - <span>{errors.confirmPassword}</span> + <span>{errors.confirmPassword.toString()}</span> ) : null} <Button @@ -311,7 +311,6 @@ export default function General(props) { color="primary" fullWidth disabled={!isValid || !dirty} - className={classes.submit} > { i18next.t( diff --git a/jams-react-client/src/views/UserProfile/DisplayUserProfile.tsx b/jams-react-client/src/views/UserProfile/DisplayUserProfile.tsx index 972d59e5..e528350b 100644 --- a/jams-react-client/src/views/UserProfile/DisplayUserProfile.tsx +++ b/jams-react-client/src/views/UserProfile/DisplayUserProfile.tsx @@ -184,6 +184,7 @@ export interface UserProfile { phoneNumberExtension: string; faxNumber: string; mobileNumber: string; + jamiId?: string; } export interface GroupMembership { @@ -197,7 +198,7 @@ export interface Group { blueprint: string; } -interface UserGroup { +interface UserGroupMapping { groupId: string; username: string; } @@ -208,7 +209,18 @@ const DisplayUserProfile: FC<DisplayUserProfileProps> = ({ }) => { const classes = useStyles(); const history = useHistory(); - const [user, setUser] = useState<UserProfile>({}); + const [user, setUser] = useState<UserProfile>({ + username: "", + firstName: "", + lastName: "", + email: "", + profilePicture: "", + organization: "", + phoneNumber: "", + phoneNumberExtension: "", + faxNumber: "", + mobileNumber: "", + }); const [groupMemberships, setGroupMemberships] = useState<GroupMembership[]>( [] ); @@ -257,6 +269,7 @@ const DisplayUserProfile: FC<DisplayUserProfileProps> = ({ }, [history, username]); const getAdminUserGroups = () => { + // TODO do this in a single sql query on the server, with a JOIN axios( configApiCall( api_path_get_admin_user_groups + username, @@ -265,15 +278,22 @@ const DisplayUserProfile: FC<DisplayUserProfileProps> = ({ null ) ).then((userGroups) => { - const userGroupsData: UserGroup[] = userGroups.data; - userGroupsData.forEach((group) => { + const userGroupsData: UserGroupMapping[] = userGroups.data; + const promises = userGroupsData.map((group) => axios( configApiCall(api_path_get_group + group.groupId, "GET", null, null) ).then((groupInfo) => { - group["name"] = groupInfo.data.name; - }); + const g: GroupMembership = { + groupId: group.groupId, + name: groupInfo.data.name, + }; + return g; + }) + ); + + Promise.all(promises).then((groupMemberships) => { + setGroupMemberships(groupMemberships); }); - setGroupMemberships(userGroupsData); }); }; diff --git a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.tsx b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.tsx index 6de3cb0f..b037e73a 100644 --- a/jams-react-client/src/views/UserProfile/EditCreateUserProfile.tsx +++ b/jams-react-client/src/views/UserProfile/EditCreateUserProfile.tsx @@ -74,6 +74,7 @@ import LinearProgress from "@mui/material/LinearProgress"; import i18next from "i18next"; import generator from "generate-password-browser"; +import { UserProfile } from "./DisplayUserProfile"; const styles = (theme) => ({ ...dashboardStyle, @@ -218,6 +219,11 @@ const styles = (theme) => ({ const useStyles = makeStyles(styles as any); +interface UserProfileForm extends UserProfile { + password?: string; + confirmPassword?: string; +} + const toBase64 = (file: File): Promise<string | undefined> => new Promise((resolve, reject) => { const reader = new FileReader(); @@ -258,7 +264,7 @@ export default function EditCreateUserProfile(props) { const intialyGeneratedPassword = passwordGenerator(); - const [initialValues, setInitialValues] = useState({ + const [initialValues, setInitialValues] = useState<UserProfileForm>({ username: "", password: intialyGeneratedPassword, confirmPassword: intialyGeneratedPassword, @@ -520,7 +526,9 @@ export default function EditCreateUserProfile(props) { 2, i18next.t("last_name_is_too_short", "Last Name is too short!") as string ), - email: Yup.string().email(i18next.t("invalid_email", "Invalid email!")), + email: Yup.string().email( + i18next.t("invalid_email", "Invalid email!") as string + ), profilePicture: Yup.string(), organization: Yup.string().min( 2, @@ -586,7 +594,7 @@ export default function EditCreateUserProfile(props) { step={0.1} aria-labelledby="Zoom" className={classes.slider} - onChange={(e, zoom) => setZoom(zoom)} + onChange={(e, zoom: any) => setZoom(zoom)} /> </div> <div className={classes.sliderContainer}> @@ -603,7 +611,7 @@ export default function EditCreateUserProfile(props) { step={1} aria-labelledby="Rotation" className={classes.slider} - onChange={(e, rotation) => setRotation(rotation)} + onChange={(e, rotation: any) => setRotation(rotation)} /> </div> </div> @@ -823,16 +831,10 @@ export default function EditCreateUserProfile(props) { </Grid> )} {props.createUser && ( - <Grid - item - align="left" - xs={12} - sm={12} - md={6} - ></Grid> + <Grid item xs={12} sm={12} md={6}></Grid> )} {props.createUser && ( - <Grid item align="left" xs={12} sm={12} md={6}> + <Grid item xs={12} sm={12} md={6}> <Button variant="contained" color="primary" @@ -897,7 +899,13 @@ export default function EditCreateUserProfile(props) { ) : null} </Grid> )} - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid + item + alignContent="center" + xs={12} + sm={12} + md={6} + > <FormikField name="firstName" label={ @@ -917,7 +925,13 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid + item + alignContent="center" + xs={12} + sm={12} + md={6} + > <FormikField name="lastName" label={ @@ -936,7 +950,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="email" label={i18next.t("email", "Email") as string} @@ -953,7 +967,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="organization" label={ @@ -976,7 +990,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="faxNumber" label={ @@ -996,7 +1010,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="phoneNumber" label={ @@ -1019,7 +1033,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="phoneNumberExtension" label={ @@ -1038,7 +1052,7 @@ export default function EditCreateUserProfile(props) { onKeyUpErrorMessage="" /> </Grid> - <Grid item align="center" xs={12} sm={12} md={6}> + <Grid item alignSelf="center" xs={12} sm={12} md={6}> <FormikField name="mobileNumber" label={i18next.t("mobile", "Mobile") as string} diff --git a/jams-react-client/src/views/UserProfile/cropImage.tsx b/jams-react-client/src/views/UserProfile/cropImage.tsx index 1bd5b8de..f626794b 100644 --- a/jams-react-client/src/views/UserProfile/cropImage.tsx +++ b/jams-react-client/src/views/UserProfile/cropImage.tsx @@ -1,4 +1,4 @@ -const createImage = (url) => +const createImage = (url: string): Promise<HTMLImageElement> => new Promise((resolve, reject) => { const image = new Image(); image.addEventListener("load", () => resolve(image)); @@ -7,7 +7,7 @@ const createImage = (url) => image.src = url; }); -function getRadianAngle(degreeValue) { +function getRadianAngle(degreeValue: number) { return (degreeValue * Math.PI) / 180; } diff --git a/jams-react-client/src/views/Users/Users.tsx b/jams-react-client/src/views/Users/Users.tsx index 301e58ee..1b3997b4 100644 --- a/jams-react-client/src/views/Users/Users.tsx +++ b/jams-react-client/src/views/Users/Users.tsx @@ -120,7 +120,7 @@ export default function Users() { }; }, [history]); - const searchUsers = (value, page = "1") => { + const searchUsers = (value, page = 1) => { setSelectedPage(page); setLoading(true); setNoMatchFound(false); -- GitLab