| "react-dom": "^17.0.2", | "react-dom": "^17.0.2", | ||||
| "react-helmet-async": "^1.0.9", | "react-helmet-async": "^1.0.9", | ||||
| "react-i18next": "^11.10.0", | "react-i18next": "^11.10.0", | ||||
| "react-idle-timer": "^5.4.2", | |||||
| "react-redux": "^7.2.4", | "react-redux": "^7.2.4", | ||||
| "react-router-dom": "^5.2.0", | "react-router-dom": "^5.2.0", | ||||
| "react-scripts": "4.0.3", | "react-scripts": "4.0.3", | ||||
| "react-select": "^4.3.1", | "react-select": "^4.3.1", | ||||
| "react-toastify": "9.0.3", | |||||
| "redux": "^4.1.0", | "redux": "^4.1.0", | ||||
| "redux-saga": "^1.1.3", | "redux-saga": "^1.1.3", | ||||
| "sass": "^1.34.1", | "sass": "^1.34.1", |
| import React from 'react'; | |||||
| import { Router } from 'react-router-dom'; | |||||
| import { Helmet } from 'react-helmet-async'; | |||||
| import i18next from 'i18next'; | |||||
| import history from './store/utils/history'; | |||||
| import AppRoutes from './AppRoutes'; | |||||
| import React from "react"; | |||||
| import { Router } from "react-router-dom"; | |||||
| import { Helmet } from "react-helmet-async"; | |||||
| import i18next from "i18next"; | |||||
| import history from "./store/utils/history"; | |||||
| import AppRoutes from "./AppRoutes"; | |||||
| const App = () => ( | |||||
| import { ToastContainer } from "react-toastify"; | |||||
| import "react-toastify/dist/ReactToastify.css"; | |||||
| const App = () => { | |||||
| return ( | |||||
| <> | <> | ||||
| <Router history={history}> | <Router history={history}> | ||||
| <Helmet> | <Helmet> | ||||
| <title> | |||||
| {i18next.t('app.title')} | |||||
| </title> | |||||
| <title>{i18next.t("app.title")}</title> | |||||
| </Helmet> | </Helmet> | ||||
| <main className="l-page"> | |||||
| <AppRoutes /> | |||||
| </main> | |||||
| <ToastContainer bodyClassName="ToastBody" /> | |||||
| <main className="l-page"> | |||||
| <AppRoutes /> | |||||
| </main> | |||||
| </Router> | </Router> | ||||
| </> | </> | ||||
| ); | ); | ||||
| }; | |||||
| export default App; | |||||
| export default App; |
| import React, { useState, useMemo, useContext } from 'react'; | |||||
| import React, { useState, useMemo, useContext } from "react"; | |||||
| import { | import { | ||||
| AppBar, | |||||
| Badge, | |||||
| Box, | |||||
| IconButton, | |||||
| Toolbar, | |||||
| Typography, | |||||
| List, | |||||
| ListItem, | |||||
| ListItemButton, | |||||
| ListItemIcon, | |||||
| ListItemText, | |||||
| useMediaQuery, | |||||
| } from '@mui/material'; | |||||
| import { useTheme } from '@mui/system'; | |||||
| import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined'; | |||||
| import ShoppingBasketIcon from '@mui/icons-material/ShoppingBasket'; | |||||
| import Brightness4Icon from '@mui/icons-material/Brightness4'; | |||||
| import Brightness7Icon from '@mui/icons-material/Brightness7'; | |||||
| import MenuList from './MenuListComponent'; | |||||
| import Drawer from './DrawerComponent'; | |||||
| import { ColorModeContext } from '../../context/ColorModeContext'; | |||||
| AppBar, | |||||
| Badge, | |||||
| Box, | |||||
| IconButton, | |||||
| Toolbar, | |||||
| Typography, | |||||
| List, | |||||
| ListItem, | |||||
| ListItemButton, | |||||
| ListItemIcon, | |||||
| ListItemText, | |||||
| useMediaQuery, | |||||
| } from "@mui/material"; | |||||
| import { useTheme } from "@mui/system"; | |||||
| import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined"; | |||||
| import ShoppingBasketIcon from "@mui/icons-material/ShoppingBasket"; | |||||
| import LogoutIcon from "@mui/icons-material/Logout"; | |||||
| import Brightness4Icon from "@mui/icons-material/Brightness4"; | |||||
| import Brightness7Icon from "@mui/icons-material/Brightness7"; | |||||
| import MenuList from "./MenuListComponent"; | |||||
| import Drawer from "./DrawerComponent"; | |||||
| import { ColorModeContext } from "../../context/ColorModeContext"; | |||||
| import { useDispatch } from "react-redux"; | |||||
| import { logoutUser } from "../../store/actions/login/loginActions"; | |||||
| const NavbarComponent = () => { | const NavbarComponent = () => { | ||||
| const [openDrawer, setOpenDrawer] = useState(false); | |||||
| const theme = useTheme(); | |||||
| const matches = useMediaQuery(theme.breakpoints.down('sm')); | |||||
| const toggleColorMode = useContext(ColorModeContext); | |||||
| const dispatch = useDispatch(); | |||||
| const [openDrawer, setOpenDrawer] = useState(false); | |||||
| const theme = useTheme(); | |||||
| const matches = useMediaQuery(theme.breakpoints.down("sm")); | |||||
| const toggleColorMode = useContext(ColorModeContext); | |||||
| const handleToggleDrawer = () => { | |||||
| setOpenDrawer(!openDrawer); | |||||
| }; | |||||
| const handleToggleDrawer = () => { | |||||
| setOpenDrawer(!openDrawer); | |||||
| }; | |||||
| const drawerContent = useMemo( | |||||
| () => ( | |||||
| <List> | |||||
| <ListItemButton divider onClick={handleToggleDrawer}> | |||||
| <ListItemIcon> | |||||
| <ListItemText>Link 1</ListItemText> | |||||
| </ListItemIcon> | |||||
| </ListItemButton> | |||||
| <ListItem divider onClick={handleToggleDrawer}> | |||||
| <ListItemIcon> | |||||
| <ListItemText>Link 2</ListItemText> | |||||
| </ListItemIcon> | |||||
| </ListItem> | |||||
| <ListItem divider onClick={handleToggleDrawer}> | |||||
| <ListItemText>Link 3</ListItemText> | |||||
| </ListItem> | |||||
| <ListItem divider> | |||||
| <IconButton onClick={toggleColorMode}> | |||||
| <ListItemText>Toggle {theme.palette.mode} mode</ListItemText> | |||||
| {theme.palette.mode === 'dark' ? ( | |||||
| <Brightness7Icon /> | |||||
| ) : ( | |||||
| <Brightness4Icon /> | |||||
| )} | |||||
| </IconButton> | |||||
| </ListItem> | |||||
| </List> | |||||
| ), | |||||
| [handleToggleDrawer] | |||||
| ); | |||||
| const handleLogout = () => { | |||||
| dispatch(logoutUser()); | |||||
| }; | |||||
| return ( | |||||
| <AppBar | |||||
| elevation={2} | |||||
| sx={{ backgroundColor: 'background.default', position: 'relative' }} | |||||
| > | |||||
| <Toolbar> | |||||
| <Box | |||||
| component="div" | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| alignItems: 'center', | |||||
| width: '100%', | |||||
| }} | |||||
| > | |||||
| {matches ? ( | |||||
| <Drawer | |||||
| open={openDrawer} | |||||
| toggleOpen={handleToggleDrawer} | |||||
| content={drawerContent} | |||||
| /> | |||||
| ) : ( | |||||
| <Box sx={{ display: 'flex' }}> | |||||
| <Typography | |||||
| variant="h6" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: 'pointer', | |||||
| color: 'text.primary', | |||||
| }} | |||||
| > | |||||
| Link 1 | |||||
| </Typography> | |||||
| <Typography | |||||
| variant="body1" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: 'pointer', | |||||
| color: 'text.primary', | |||||
| }} | |||||
| > | |||||
| Link 2 | |||||
| </Typography> | |||||
| <Typography | |||||
| variant="subtitle1" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: 'pointer', | |||||
| color: 'text.primary', | |||||
| }} | |||||
| > | |||||
| Link 3 | |||||
| </Typography> | |||||
| </Box> | |||||
| )} | |||||
| <Box> | |||||
| <MenuList /> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center', | |||||
| }} | |||||
| > | |||||
| {matches ? ( | |||||
| <Box> | |||||
| <IconButton onClick={handleToggleDrawer}> | |||||
| <MenuOutlinedIcon /> | |||||
| </IconButton> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Box> | |||||
| <IconButton> | |||||
| <Badge badgeContent={3} color="primary"> | |||||
| <ShoppingBasketIcon color="action" /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <IconButton sx={{ ml: 1 }} onClick={toggleColorMode}> | |||||
| {theme.palette.mode === 'dark' ? ( | |||||
| <Brightness7Icon /> | |||||
| ) : ( | |||||
| <Brightness4Icon /> | |||||
| )} | |||||
| </IconButton> | |||||
| </Box> | |||||
| )} | |||||
| </Box> | |||||
| </Box> | |||||
| </Toolbar> | |||||
| </AppBar> | |||||
| ); | |||||
| const drawerContent = useMemo( | |||||
| () => ( | |||||
| <List> | |||||
| <ListItemButton divider onClick={handleToggleDrawer}> | |||||
| <ListItemIcon> | |||||
| <ListItemText>Link 1</ListItemText> | |||||
| </ListItemIcon> | |||||
| </ListItemButton> | |||||
| <ListItem divider onClick={handleToggleDrawer}> | |||||
| <ListItemIcon> | |||||
| <ListItemText>Link 2</ListItemText> | |||||
| </ListItemIcon> | |||||
| </ListItem> | |||||
| <ListItem divider onClick={handleToggleDrawer}> | |||||
| <ListItemText>Link 3</ListItemText> | |||||
| </ListItem> | |||||
| <ListItem divider> | |||||
| <IconButton onClick={toggleColorMode}> | |||||
| <ListItemText>Toggle {theme.palette.mode} mode</ListItemText> | |||||
| {theme.palette.mode === "dark" ? ( | |||||
| <Brightness7Icon /> | |||||
| ) : ( | |||||
| <Brightness4Icon /> | |||||
| )} | |||||
| </IconButton> | |||||
| </ListItem> | |||||
| </List> | |||||
| ), | |||||
| [handleToggleDrawer] | |||||
| ); | |||||
| return ( | |||||
| <AppBar | |||||
| elevation={2} | |||||
| sx={{ backgroundColor: "background.default", position: "relative" }} | |||||
| > | |||||
| <Toolbar> | |||||
| <Box | |||||
| component="div" | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| alignItems: "center", | |||||
| width: "100%", | |||||
| }} | |||||
| > | |||||
| {matches ? ( | |||||
| <Drawer | |||||
| open={openDrawer} | |||||
| toggleOpen={handleToggleDrawer} | |||||
| content={drawerContent} | |||||
| /> | |||||
| ) : ( | |||||
| <Box sx={{ display: "flex" }}> | |||||
| <Typography | |||||
| variant="h6" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: "pointer", | |||||
| color: "text.primary", | |||||
| }} | |||||
| > | |||||
| Link 1 | |||||
| </Typography> | |||||
| <Typography | |||||
| variant="body1" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: "pointer", | |||||
| color: "text.primary", | |||||
| }} | |||||
| > | |||||
| Link 2 | |||||
| </Typography> | |||||
| <Typography | |||||
| variant="subtitle1" | |||||
| sx={{ | |||||
| marginRight: 3, | |||||
| cursor: "pointer", | |||||
| color: "text.primary", | |||||
| }} | |||||
| > | |||||
| Link 3 | |||||
| </Typography> | |||||
| </Box> | |||||
| )} | |||||
| <Box> | |||||
| <MenuList /> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "center", | |||||
| alignItems: "center", | |||||
| }} | |||||
| > | |||||
| {matches ? ( | |||||
| <Box> | |||||
| <IconButton onClick={handleToggleDrawer}> | |||||
| <MenuOutlinedIcon /> | |||||
| </IconButton> | |||||
| </Box> | |||||
| ) : ( | |||||
| <Box> | |||||
| <IconButton> | |||||
| <Badge badgeContent={3} color="primary"> | |||||
| <ShoppingBasketIcon color="action" /> | |||||
| </Badge> | |||||
| </IconButton> | |||||
| <IconButton sx={{ ml: 1 }} onClick={toggleColorMode}> | |||||
| {theme.palette.mode === "dark" ? ( | |||||
| <Brightness7Icon /> | |||||
| ) : ( | |||||
| <Brightness4Icon /> | |||||
| )} | |||||
| </IconButton> | |||||
| <IconButton onClick={handleLogout}> | |||||
| <LogoutIcon /> | |||||
| </IconButton> | |||||
| </Box> | |||||
| )} | |||||
| </Box> | |||||
| </Box> | |||||
| </Toolbar> | |||||
| </AppBar> | |||||
| ); | |||||
| }; | }; | ||||
| export default NavbarComponent; | export default NavbarComponent; |
| error: 'Error', | error: 'Error', | ||||
| continue: 'Continue', | continue: 'Continue', | ||||
| labelUsername: 'Username', | labelUsername: 'Username', | ||||
| labelEmail: 'Email', | |||||
| labelPassword: 'Password', | labelPassword: 'Password', | ||||
| next: 'Next', | next: 'Next', | ||||
| nextPage: 'Next page', | nextPage: 'Next page', | ||||
| }, | }, | ||||
| apiErrors:{ | apiErrors:{ | ||||
| ClientIpAddressIsNullOrEmpty:"Client Ip address is null or empty", | ClientIpAddressIsNullOrEmpty:"Client Ip address is null or empty", | ||||
| UsernameDoesNotExist: "Username does not exist" | |||||
| UsernameDoesNotExist: "Username does not exist", | |||||
| WrongCredentials: "Wrong credentials", | |||||
| SomethingWentWrong: "Something went wrong", | |||||
| WrongPasswordAccountIsLocked: "Wrong credentials, account is locked", | |||||
| AccountIsLocked: "Account is locked" | |||||
| } | } | ||||
| }; | }; |
| export default { | |||||
| email: "", | |||||
| }; | |||||
| export default { | |||||
| email: "", | |||||
| password: "", | |||||
| }; |
| import React from 'react'; | import React from 'react'; | ||||
| import { useFormik } from 'formik'; | import { useFormik } from 'formik'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import * as Yup from 'yup'; | |||||
| import i18next from 'i18next'; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Container, | Container, | ||||
| import Backdrop from '../../components/MUI/BackdropComponent'; | import Backdrop from '../../components/MUI/BackdropComponent'; | ||||
| import { LOGIN_PAGE } from '../../constants/pages'; | import { LOGIN_PAGE } from '../../constants/pages'; | ||||
| import { NavLink } from 'react-router-dom'; | import { NavLink } from 'react-router-dom'; | ||||
| const forgotPasswordValidationSchema = Yup.object().shape({ | |||||
| email: Yup.string() | |||||
| .required(i18next.t('forgotPassword.emailRequired')) | |||||
| .email(i18next.t('forgotPassword.emailFormat')), | |||||
| }); | |||||
| import forgotPasswordValidation from '../../validations/forgotPasswordValidation'; | |||||
| import forgotPasswordInitialValues from '../../initialValues/forgotPasswordInitialValues'; | |||||
| const ForgotPasswordPage = () => { | const ForgotPasswordPage = () => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| }; | }; | ||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | |||||
| email: '', | |||||
| }, | |||||
| validationSchema: forgotPasswordValidationSchema, | |||||
| initialValues: forgotPasswordInitialValues, | |||||
| validationSchema: forgotPasswordValidation, | |||||
| onSubmit: handleSubmit, | onSubmit: handleSubmit, | ||||
| validateOnBlur: true, | validateOnBlur: true, | ||||
| enableReinitialize: true, | enableReinitialize: true, |
| import React from 'react'; | |||||
| import { Box, Grid } from '@mui/material'; | |||||
| import Navbar from '../../components/MUI/NavbarComponent'; | |||||
| import Modals from '../../components/MUI/Examples/ModalsExample'; | |||||
| import DataGrid from '../../components/MUI/Examples/DataGridExample'; | |||||
| import PagingSortingFiltering from '../../components/MUI/Examples/PagingSortingFilteringExample'; | |||||
| import PagingSortingFilteringServerSide from '../../components/MUI/Examples/PagingSortingFilteringExampleServerSide'; | |||||
| import RandomDataProvider from '../../context/RandomDataContext'; | |||||
| import React from "react"; | |||||
| import { Box, Grid } from "@mui/material"; | |||||
| import Navbar from "../../components/MUI/NavbarComponent"; | |||||
| import Modals from "../../components/MUI/Examples/ModalsExample"; | |||||
| import DataGrid from "../../components/MUI/Examples/DataGridExample"; | |||||
| import PagingSortingFiltering from "../../components/MUI/Examples/PagingSortingFilteringExample"; | |||||
| import PagingSortingFilteringServerSide from "../../components/MUI/Examples/PagingSortingFilteringExampleServerSide"; | |||||
| import RandomDataProvider from "../../context/RandomDataContext"; | |||||
| const HomePage = () => { | const HomePage = () => { | ||||
| return ( | |||||
| <> | |||||
| <Navbar /> | |||||
| <Box sx={{ mt: 4, mx: 4 }}> | |||||
| <Grid container spacing={2} justifyContent="center"> | |||||
| <Grid item xs={12} md={3}> | |||||
| <Modals /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={6}> | |||||
| <DataGrid /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={9}> | |||||
| <PagingSortingFiltering /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={9}> | |||||
| {/* Move to higher components? */} | |||||
| <RandomDataProvider> | |||||
| <PagingSortingFilteringServerSide /> | |||||
| </RandomDataProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </> | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| <Navbar /> | |||||
| <Box sx={{ mt: 4, mx: 4 }}> | |||||
| <Grid container spacing={2} justifyContent="center"> | |||||
| <Grid item xs={12} md={3}> | |||||
| <Modals /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={6}> | |||||
| <DataGrid /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={9}> | |||||
| <PagingSortingFiltering /> | |||||
| </Grid> | |||||
| <Grid item xs={12} md={9}> | |||||
| {/* Move to higher components? */} | |||||
| <RandomDataProvider> | |||||
| <PagingSortingFilteringServerSide /> | |||||
| </RandomDataProvider> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </> | |||||
| ); | |||||
| }; | }; | ||||
| export default HomePage; | export default HomePage; |
| /* eslint-disable */ | /* eslint-disable */ | ||||
| import React, { useState } from 'react'; | |||||
| import React, { useEffect, useState } from 'react'; | |||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { useFormik } from 'formik'; | import { useFormik } from 'formik'; | ||||
| import { useDispatch, useSelector } from 'react-redux'; | import { useDispatch, useSelector } from 'react-redux'; | ||||
| import { NavLink } from 'react-router-dom'; | import { NavLink } from 'react-router-dom'; | ||||
| import * as Yup from 'yup'; | import * as Yup from 'yup'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import i18next from 'i18next'; | |||||
| import { | import { | ||||
| clearLoginErrors, | clearLoginErrors, | ||||
| fetchUser, | fetchUser, | ||||
| import ErrorMessage from '../../components/MUI/ErrorMessageComponent'; | import ErrorMessage from '../../components/MUI/ErrorMessageComponent'; | ||||
| import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors'; | import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors'; | ||||
| import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants'; | import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants'; | ||||
| import loginValidation from '../../validations/loginValidation'; | |||||
| import loginInitialValues from '../../initialValues/loginInitialValues'; | |||||
| const LoginPage = ({ history }) => { | const LoginPage = ({ history }) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | const handleClickShowPassword = () => setShowPassword(!showPassword); | ||||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | const handleMouseDownPassword = () => setShowPassword(!showPassword); | ||||
| // When user refreshes page | |||||
| // useEffect(() => { | |||||
| // function redirectClient() { | |||||
| // if (!tokens.RefreshToken && !tokens.JwtToken) { | |||||
| // return; | |||||
| // } | |||||
| // } | |||||
| // redirectClient(); | |||||
| // }, [history, tokens]); | |||||
| // Clear login errors when user firstly enters the page | |||||
| useEffect(() => { | |||||
| dispatch(clearLoginErrors()) | |||||
| }, []) | |||||
| const isLoading = useSelector( | const isLoading = useSelector( | ||||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | selectIsLoadingByActionType(LOGIN_USER_LOADING) | ||||
| }; | }; | ||||
| const handleSubmit = (values) => { | const handleSubmit = (values) => { | ||||
| const { username: Username, password: Password } = values; | |||||
| const { email: Username, password: Password } = values; | |||||
| dispatch(clearLoginErrors()); | dispatch(clearLoginErrors()); | ||||
| dispatch( | dispatch( | ||||
| fetchUser({ | fetchUser({ | ||||
| }; | }; | ||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | |||||
| username: '', | |||||
| password: '', | |||||
| }, | |||||
| validationSchema: Yup.object().shape({ | |||||
| username: Yup.string().required(t('login.usernameRequired')), | |||||
| password: Yup.string().required(t('login.passwordRequired')), | |||||
| }), | |||||
| initialValues: loginInitialValues, | |||||
| validationSchema: loginValidation, | |||||
| onSubmit: handleSubmit, | onSubmit: handleSubmit, | ||||
| validateOnBlur: true, | validateOnBlur: true, | ||||
| enableReinitialize: true, | enableReinitialize: true, | ||||
| > | > | ||||
| <Backdrop position="absolute" isLoading={isLoading} /> | <Backdrop position="absolute" isLoading={isLoading} /> | ||||
| <TextField | <TextField | ||||
| name="username" | |||||
| label={t('common.labelUsername')} | |||||
| name="email" | |||||
| label={t('common.labelEmail')} | |||||
| margin="normal" | margin="normal" | ||||
| value={formik.values.username} | |||||
| value={formik.values.email} | |||||
| onChange={formik.handleChange} | onChange={formik.handleChange} | ||||
| error={formik.touched.username && Boolean(formik.errors.username)} | |||||
| helperText={formik.touched.username && formik.errors.username} | |||||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||||
| helperText={formik.touched.email && formik.errors.email} | |||||
| autoFocus | autoFocus | ||||
| fullWidth | fullWidth | ||||
| /> | /> |
| delete request.defaults.headers.Authorization; | delete request.defaults.headers.Authorization; | ||||
| }; | }; | ||||
| export const attachPostRequestListener = (postRequestListener) => { | |||||
| request.interceptors.response.use( | |||||
| // If you pass function to interceptor of axios, it only adds that function | |||||
| // to existing array of interceptor functions. That causes that same function | |||||
| // of interceptors getting called multiple times instead of just one time, as it | |||||
| // is supposed to do. Thats why there is 'global' axios interceptor array, which indicates | |||||
| // axios to eject previous interceptor. This approach requires that every middleware has its | |||||
| // unique name from which it is being recognized. Every object in those arrays contains | |||||
| // interceptor name and ID of interceptor function. | |||||
| let axiosInterceptorRequests = []; | |||||
| let axiosInterceptorResponses = []; | |||||
| export const attachPostRequestListener = ( | |||||
| postRequestListener, | |||||
| interceptorName | |||||
| ) => { | |||||
| let previousAxiosInterceptor = axiosInterceptorResponses.find( | |||||
| (item) => item.name === interceptorName | |||||
| ); | |||||
| let previousAxiosInterceptorResponses = axiosInterceptorResponses; | |||||
| if (previousAxiosInterceptor !== undefined) { | |||||
| request.interceptors.response.eject(previousAxiosInterceptor.interceptorID); | |||||
| previousAxiosInterceptorResponses = axiosInterceptorResponses.filter( | |||||
| (item) => item.interceptorID !== previousAxiosInterceptor.interceptorID | |||||
| ); | |||||
| } | |||||
| let axiosInterceptorID = request.interceptors.response.use( | |||||
| (response) => response, | (response) => response, | ||||
| (response) => postRequestListener(response), | |||||
| (response) => postRequestListener(response) | |||||
| ); | |||||
| previousAxiosInterceptorResponses.push({ | |||||
| name: interceptorName, | |||||
| interceptorID: axiosInterceptorID, | |||||
| }); | |||||
| axiosInterceptorResponses = [...previousAxiosInterceptorResponses]; | |||||
| }; | |||||
| export const attachBeforeRequestListener = ( | |||||
| beforeRequestListener, | |||||
| interceptorName | |||||
| ) => { | |||||
| let previousAxiosInterceptor = axiosInterceptorRequests.find( | |||||
| (item) => item.name === interceptorName | |||||
| ); | |||||
| let previousAxiosInterceptorRequests = axiosInterceptorRequests; | |||||
| if (previousAxiosInterceptor !== undefined) { | |||||
| request.interceptors.request.eject(previousAxiosInterceptor.interceptorID); | |||||
| previousAxiosInterceptorRequests = axiosInterceptorRequests.filter( | |||||
| (item) => item.interceptorID !== previousAxiosInterceptor.interceptorID | |||||
| ); | |||||
| } | |||||
| let axiosInterceptorID = request.interceptors.request.use( | |||||
| (response) => beforeRequestListener(response), | |||||
| (response) => response | |||||
| ); | ); | ||||
| previousAxiosInterceptorRequests.push({ | |||||
| name: interceptorName, | |||||
| interceptorID: axiosInterceptorID, | |||||
| }); | |||||
| axiosInterceptorRequests = [...previousAxiosInterceptorRequests]; | |||||
| }; | }; | ||||
| export const apiDefaultUrl = request.defaults.baseURL; | export const apiDefaultUrl = request.defaults.baseURL; |
| import { applyMiddleware, compose, createStore } from 'redux'; | |||||
| import createSagaMiddleware from 'redux-saga'; | |||||
| import rootReducer from './reducers'; | |||||
| import rootSaga from './saga'; | |||||
| import loadingMiddleware from './middleware/loadingMiddleware'; | |||||
| import requestStatusMiddleware from './middleware/requestStatusMiddleware'; | |||||
| import internalServerErrorMiddleware from './middleware/internalServerErrorMiddleware'; | |||||
| import { applyMiddleware, compose, createStore } from "redux"; | |||||
| import createSagaMiddleware from "redux-saga"; | |||||
| import rootReducer from "./reducers"; | |||||
| import rootSaga from "./saga"; | |||||
| import loadingMiddleware from "./middleware/loadingMiddleware"; | |||||
| import requestStatusMiddleware from "./middleware/requestStatusMiddleware"; | |||||
| import internalServerErrorMiddleware from "./middleware/internalServerErrorMiddleware"; | |||||
| // import accessTokenMiddleware from "./middleware/accessTokenMiddleware"; | |||||
| // import authenticationMiddleware from "./middleware/authenticationMiddleware"; | |||||
| const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; | |||||
| const composeEnhancers = | |||||
| (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && | |||||
| window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ | |||||
| trace: true, | |||||
| traceLimit: 25, | |||||
| })) || | |||||
| compose; | |||||
| const sagaMiddleware = createSagaMiddleware(); | const sagaMiddleware = createSagaMiddleware(); | ||||
| export default createStore( | export default createStore( | ||||
| rootReducer, | rootReducer, | ||||
| sagaMiddleware, | sagaMiddleware, | ||||
| loadingMiddleware, | loadingMiddleware, | ||||
| requestStatusMiddleware, | requestStatusMiddleware, | ||||
| internalServerErrorMiddleware, | |||||
| ), | |||||
| ), | |||||
| internalServerErrorMiddleware | |||||
| ) | |||||
| ) | |||||
| ); | ); | ||||
| sagaMiddleware.run(rootSaga); | sagaMiddleware.run(rootSaga); |
| import jwt from "jsonwebtoken"; | |||||
| import { JWT_TOKEN } from "../../constants/localStorage"; | |||||
| import { attachBeforeRequestListener } from "../../request/index"; | |||||
| import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | |||||
| import { refreshUserToken } from "../actions/login/loginActions"; | |||||
| export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR"; | |||||
| export default ({ dispatch }) => | |||||
| (next) => | |||||
| (action) => { | |||||
| attachBeforeRequestListener(async (response) => { | |||||
| const jwtToken = authScopeStringGetHelper(JWT_TOKEN); | |||||
| const jwtTokenDecoded = jwt.decode(jwtToken); | |||||
| if (!response.headers?.Authorization) { | |||||
| response.headers.Authorization = `Bearer ${jwtToken}`; | |||||
| } | |||||
| // If access token is expired, refresh access token | |||||
| if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) { | |||||
| console.log('response', response) | |||||
| dispatch(refreshUserToken()); | |||||
| } | |||||
| return Promise.resolve(response); | |||||
| }, accessTokensMiddlewareInterceptorName); | |||||
| next(action); | |||||
| }; |
| import i18next from "i18next"; | |||||
| import { attachPostRequestListener } from "../../request"; | |||||
| import { logoutUser } from "../actions/login/loginActions"; | |||||
| import { makeErrorToastMessage } from "../../util/helpers/toastMessage"; | |||||
| export const authenticationMiddlewareInterceptorName = | |||||
| "AUTHENTICATION_MIDDLEWARE"; | |||||
| export default ({ dispatch }) => | |||||
| (next) => | |||||
| (action) => { | |||||
| attachPostRequestListener((error) => { | |||||
| if (!error.response) { | |||||
| makeErrorToastMessage(i18next.t("apiErrors.SomethingWentWrong")); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| if (error.response.status === 401) { | |||||
| dispatch(logoutUser()); | |||||
| return Promise.reject(error); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, authenticationMiddlewareInterceptorName); | |||||
| next(action); | |||||
| }; |
| import { ERROR_PAGE } from '../../constants/pages'; | |||||
| import { attachPostRequestListener } from '../../request'; | |||||
| import history from '../utils/history'; | |||||
| import { attachPostRequestListener } from "../../request"; | |||||
| import { makeErrorToastMessage } from "../../util/helpers/toastMessage"; | |||||
| import i18next from "i18next"; | |||||
| export const serverErrorMiddlewareInterceptorName = | |||||
| "INTERNAL_SERVER_ERROR_MIDDLEWARE_INTERCEPTOR"; | |||||
| export default () => (next) => (action) => { | export default () => (next) => (action) => { | ||||
| attachPostRequestListener((error) => { | attachPostRequestListener((error) => { | ||||
| if (!error.response) { | if (!error.response) { | ||||
| return Promise.reject(error); | |||||
| return makeErrorToastMessage(i18next.t("apiErrors.SomethingWentWrong")); | |||||
| } | } | ||||
| if (error.response.status === 500) { | if (error.response.status === 500) { | ||||
| return history.push(ERROR_PAGE); | |||||
| return makeErrorToastMessage(i18next.t("apiErrors.SomethingWentWrong")); | |||||
| } | } | ||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| }); | |||||
| }, serverErrorMiddlewareInterceptorName); | |||||
| next(action); | next(action); | ||||
| }; | }; |
| import { attachPostRequestListener } from '../../request'; | |||||
| import apiEndpoints from '../../request/apiEndpoints'; | |||||
| import { logoutUser } from '../actions/login/loginActions'; | |||||
| import { attachPostRequestListener } from "../../request"; | |||||
| import apiEndpoints from "../../request/apiEndpoints"; | |||||
| import { logoutUser } from "../actions/login/loginActions"; | |||||
| export default ({ dispatch }) => (next) => (action) => { | |||||
| attachPostRequestListener((error) => { | |||||
| if (!error.response) { | |||||
| export const requestStatusMiddlewareInterceptorName = | |||||
| "REQUEST_STATUS_MIDDLEWARE_INTERCEPTOR"; | |||||
| export default ({ dispatch }) => | |||||
| (next) => | |||||
| (action) => { | |||||
| attachPostRequestListener((error) => { | |||||
| if (!error.response) { | |||||
| return Promise.reject(error); | |||||
| } | |||||
| if ( | |||||
| error.response.config.url !== apiEndpoints.authentications.login && | |||||
| error.response.config.url !== | |||||
| apiEndpoints.authentications.confirmSecurityQuestion && | |||||
| error.response.status === 401 | |||||
| ) { | |||||
| return dispatch(logoutUser()); | |||||
| } | |||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| } | |||||
| if ( | |||||
| error.response.config.url !== apiEndpoints.authentications.login && | |||||
| error.response.config.url !== | |||||
| apiEndpoints.authentications.confirmSecurityQuestion && | |||||
| error.response.status === 401 | |||||
| ) { | |||||
| return dispatch(logoutUser()); | |||||
| } | |||||
| return Promise.reject(error); | |||||
| }); | |||||
| }, requestStatusMiddlewareInterceptorName); | |||||
| next(action); | |||||
| }; | |||||
| next(action); | |||||
| }; |
| } | } | ||||
| } catch (e) { | } catch (e) { | ||||
| if (e.response && e.response.data) { | if (e.response && e.response.data) { | ||||
| if (payload.handleApiResponseSuccess) { | |||||
| yield call(payload.handleApiResponseSuccess); | |||||
| } | |||||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | const errorMessage = yield call(rejectErrorCodeHelper, e); | ||||
| yield put(fetchUserError(errorMessage)); | yield put(fetchUserError(errorMessage)); | ||||
| } | } |
| import history from "../../store/utils/history"; | |||||
| export const routeMatches = (route, secondRoute = null) => { | |||||
| let routeToCheck = secondRoute || history.location.pathname; | |||||
| if ( | |||||
| routeToCheck === route || | |||||
| routeToCheck + "/" === route || | |||||
| routeToCheck.slice(0, -1) === route | |||||
| ) { | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| }; | |||||
| export const replaceInRoute = (route, pathVariables = {}) => { | |||||
| const keys = Object.keys(pathVariables); | |||||
| return keys.reduce( | |||||
| (acc, key) => acc.replace(`:${key}`, pathVariables[`${key}`]), | |||||
| route | |||||
| ); | |||||
| }; | |||||
| export const dynamicRouteMatches = (dynamicRoute) => { | |||||
| let indexOfDynamicChar = dynamicRoute.indexOf(":"); | |||||
| if (indexOfDynamicChar === -1) return false; | |||||
| const charactersToDelete = (dynamicRoute.length - indexOfDynamicChar) * -1; | |||||
| const newDynamicRoute = dynamicRoute.slice(0, charactersToDelete); | |||||
| return history.location.pathname.includes(newDynamicRoute); | |||||
| }; | |||||
| export const isInRoute = (routeToCheck) => { | |||||
| return ( | |||||
| history.location.pathname.includes(routeToCheck) || | |||||
| dynamicRouteMatches(routeToCheck) | |||||
| ); | |||||
| }; |
| import { toast } from "react-toastify"; | |||||
| const defaultOptions = { | |||||
| position: "top-center", | |||||
| autoClose: 3000, | |||||
| hideProgressBar: true, | |||||
| closeOnClick: true, | |||||
| pauseOnHover: true, | |||||
| pauseOnFocusLoss: false, | |||||
| draggable: true, | |||||
| }; | |||||
| export const makeToastMessage = (message, options = defaultOptions) => toast(message, options); | |||||
| export const makeErrorToastMessage = (message, options = defaultOptions) => toast.error(message, options); |
| import * as Yup from "yup"; | |||||
| import i18next from "i18next"; | |||||
| export default Yup.object().shape({ | |||||
| email: Yup.string() | |||||
| .email(i18next.t("login.emailFormat")) | |||||
| .required(i18next.t("login.emailRequired")), | |||||
| }); |
| import * as Yup from "yup"; | |||||
| import i18next from "i18next"; | |||||
| export default Yup.object().shape({ | |||||
| email: Yup.string() | |||||
| .email(i18next.t("login.emailFormat")) | |||||
| .required(i18next.t("login.emailRequired")), | |||||
| password: Yup.string() | |||||
| .required(i18next.t("login.passwordRequired")) | |||||
| .min(8, i18next.t("login.passwordLength")), | |||||
| }); |
| "@babel/runtime" "^7.14.0" | "@babel/runtime" "^7.14.0" | ||||
| html-parse-stringify "^3.0.1" | html-parse-stringify "^3.0.1" | ||||
| react-idle-timer@^5.4.2: | |||||
| version "5.4.2" | |||||
| resolved "https://registry.yarnpkg.com/react-idle-timer/-/react-idle-timer-5.4.2.tgz#69e20044cf2ecc421aef99cd82298c526d8acf37" | |||||
| integrity sha512-ofCS/qpFjm6ZguEyePvtf9YMDnLj7zZfeLXRWGRpsC6Ga47H4dm7EvoUW8MsozGEGy8zCvPK0Sk6YuAnwLEzRQ== | |||||
| react-input-autosize@^3.0.0: | react-input-autosize@^3.0.0: | ||||
| version "3.0.0" | version "3.0.0" | ||||
| resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" | resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" | ||||
| react-input-autosize "^3.0.0" | react-input-autosize "^3.0.0" | ||||
| react-transition-group "^4.3.0" | react-transition-group "^4.3.0" | ||||
| react-toastify@9.0.3: | |||||
| version "9.0.3" | |||||
| resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.0.3.tgz#8e6d22651c85cb584c5ebd0b5e2c3bf0d7ec06ee" | |||||
| integrity sha512-0QZJk0SqYBxouRBGCFU3ymvjlwimRRhVH7SzqGRiVrQ001KSoUNbGKx9Yq42aoPv18n45yJzEFG82zqv3HnASg== | |||||
| dependencies: | |||||
| clsx "^1.1.1" | |||||
| react-transition-group@^4.3.0, react-transition-group@^4.4.2: | react-transition-group@^4.3.0, react-transition-group@^4.4.2: | ||||
| version "4.4.2" | version "4.4.2" | ||||
| resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" | resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" |