| { | { | ||||
| "extends": [ | |||||
| "react-app", | |||||
| "airbnb", | |||||
| "prettier" | |||||
| ], | |||||
| "plugins": [ | |||||
| "react", | |||||
| "react-hooks", | |||||
| "security" | |||||
| ], | |||||
| "extends": ["react-app", "airbnb", "prettier"], | |||||
| "settings": { | |||||
| "import/resolver": { | |||||
| "node": { | |||||
| "paths": ["src"] | |||||
| } | |||||
| } | |||||
| }, | |||||
| "plugins": ["react", "react-hooks", "security"], | |||||
| "rules": { | "rules": { | ||||
| "react/jsx-filename-extension": "off", | "react/jsx-filename-extension": "off", | ||||
| "react/jsx-props-no-spreading": "off", | "react/jsx-props-no-spreading": "off", | ||||
| "no-use-before-define": "off", | "no-use-before-define": "off", | ||||
| "no-template-curly-in-string": "off", | "no-template-curly-in-string": "off", | ||||
| "react-hooks/exhaustive-deps": "warn", | "react-hooks/exhaustive-deps": "warn", | ||||
| "prettier/prettier": ["error", { | |||||
| "endOfLine":"auto" | |||||
| }] | |||||
| "prettier/prettier": [ | |||||
| "error", | |||||
| { | |||||
| "endOfLine": "auto" | |||||
| } | |||||
| ] | |||||
| } | } | ||||
| } | } |
| { | { | ||||
| "env": { | |||||
| "browser": true, | |||||
| "es2021": true | |||||
| }, | |||||
| "extends": [ | |||||
| "eslint:recommended", | |||||
| "plugin:react/recommended" | |||||
| ], | |||||
| "parserOptions": { | |||||
| "ecmaFeatures": { | |||||
| "jsx": true | |||||
| }, | |||||
| "ecmaVersion": 12, | |||||
| "sourceType": "module" | |||||
| }, | |||||
| "plugins": [ | |||||
| "react" | |||||
| ], | |||||
| "rules": { | |||||
| "env": { | |||||
| "browser": true, | |||||
| "es2021": true | |||||
| }, | |||||
| "extends": ["eslint:recommended", "plugin:react/recommended"], | |||||
| "settings": { | |||||
| "import/resolver": { | |||||
| "node": { | |||||
| "paths": ["src"] | |||||
| } | |||||
| } | } | ||||
| }, | |||||
| "parserOptions": { | |||||
| "ecmaFeatures": { | |||||
| "jsx": true | |||||
| }, | |||||
| "ecmaVersion": 12, | |||||
| "sourceType": "module" | |||||
| }, | |||||
| "plugins": ["react"], | |||||
| "rules": {} | |||||
| } | } |
| { | |||||
| "compilerOptions": { | |||||
| "baseUrl": "src" | |||||
| }, | |||||
| "include": ["src"] | |||||
| } |
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { ErrorMessage } from 'formik'; | import { ErrorMessage } from 'formik'; | ||||
| import IconButton from '../IconButton/IconButton'; | import IconButton from '../IconButton/IconButton'; | ||||
| import { ReactComponent as Search } from '../../assets/images/svg/search.svg'; | |||||
| import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg'; | |||||
| import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg'; | |||||
| import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg'; | |||||
| import { ReactComponent as Search } from 'assets/images/svg/search.svg'; | |||||
| import { ReactComponent as EyeOn } from 'assets/images/svg/eye-on.svg'; | |||||
| import { ReactComponent as EyeOff } from 'assets/images/svg/eye-off.svg'; | |||||
| import { ReactComponent as CapsLock } from 'assets/images/svg/caps-lock.svg'; | |||||
| const BaseInputField = ({ | const BaseInputField = ({ | ||||
| type, | type, |
| import React from 'react'; | import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { ReactComponent as Checked } from '../../assets/images/svg/checked.svg'; | |||||
| import { ReactComponent as Unchecked } from '../../assets/images/svg/unchecked.svg'; | |||||
| import { ReactComponent as Checked } from 'assets/images/svg/checked.svg'; | |||||
| import { ReactComponent as Unchecked } from 'assets/images/svg/unchecked.svg'; | |||||
| const Checkbox = ({ className, children, name, onChange, checked, field }) => ( | const Checkbox = ({ className, children, name, onChange, checked, field }) => ( | ||||
| <label htmlFor={name} className={`c-checkbox ${className || ''}`}> | <label htmlFor={name} className={`c-checkbox ${className || ''}`}> |
| import React, { useEffect, useRef } from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { ErrorMessage, useField } from 'formik'; | |||||
| import CurrencyInput from 'react-currency-input-field'; | |||||
| import { formatMoneyNumeral } from '../../util/helpers/numeralHelpers'; | |||||
| import React, { useEffect, useRef } from "react"; | |||||
| import PropTypes from "prop-types"; | |||||
| import { ErrorMessage, useField } from "formik"; | |||||
| import CurrencyInput from "react-currency-input-field"; | |||||
| import { formatMoneyNumeral } from "util/helpers/numeralHelpers"; | |||||
| import { | import { | ||||
| PLUS_SYMBOL, | PLUS_SYMBOL, | ||||
| MINUS_SYMBOL, | MINUS_SYMBOL, | ||||
| NUMPAD_MINUS_SYMBOL, | NUMPAD_MINUS_SYMBOL, | ||||
| NUMPAD_PLUS_SYMBOL, | NUMPAD_PLUS_SYMBOL, | ||||
| K_KEYCODE, | K_KEYCODE, | ||||
| } from '../../constants/keyCodeConstants'; | |||||
| } from "constants/keyCodeConstants"; | |||||
| const CurrencyField = ({ | const CurrencyField = ({ | ||||
| autoFocus, | autoFocus, | ||||
| const [field, meta] = useField(props); | const [field, meta] = useField(props); | ||||
| const inputField = useRef(null); | const inputField = useRef(null); | ||||
| function styles() { | function styles() { | ||||
| let style = 'c-currency-field'; | |||||
| let style = "c-currency-field"; | |||||
| if (meta.error && meta.touched) { | if (meta.error && meta.touched) { | ||||
| style += ` c-currency-field--error`; | style += ` c-currency-field--error`; | ||||
| const prefix = formatMoneyNumeral(0); | const prefix = formatMoneyNumeral(0); | ||||
| const prefixSymbol = () => { | const prefixSymbol = () => { | ||||
| if (prefix.includes('CAD')) { | |||||
| return 'CAD '; | |||||
| if (prefix.includes("CAD")) { | |||||
| return "CAD "; | |||||
| } | } | ||||
| return '$'; | |||||
| return "$"; | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| {...props} | {...props} | ||||
| prefix={prefixSymbol()} | prefix={prefixSymbol()} | ||||
| onValueChange={(value) => { | onValueChange={(value) => { | ||||
| onChange(value ? Number(value) : ''); | |||||
| onChange(value ? Number(value) : ""); | |||||
| }} | }} | ||||
| onKeyDown={(event) => onKeydownHandler(event)} | onKeyDown={(event) => onKeydownHandler(event)} | ||||
| ref={inputField} | ref={inputField} | ||||
| {...props} | {...props} | ||||
| prefix={prefixSymbol()} | prefix={prefixSymbol()} | ||||
| onValueChange={(value) => { | onValueChange={(value) => { | ||||
| onChange(value ? Number(value) : ''); | |||||
| onChange(value ? Number(value) : ""); | |||||
| }} | }} | ||||
| onKeyDown={(event) => onKeydownHandler(event)} | onKeyDown={(event) => onKeydownHandler(event)} | ||||
| ref={inputField} | ref={inputField} |
| NUMPAD_PLUS_SYMBOL, | NUMPAD_PLUS_SYMBOL, | ||||
| DOWN_ARROW_KEYCODE, | DOWN_ARROW_KEYCODE, | ||||
| UP_ARROW_KEYCODE, | UP_ARROW_KEYCODE, | ||||
| } from '../../constants/keyCodeConstants'; | |||||
| } from 'constants/keyCodeConstants'; | |||||
| const NumberField = ({ | const NumberField = ({ | ||||
| field, | field, |
| import React from 'react'; | import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { ReactComponent as RadioOn } from '../../assets/images/svg/radio-on.svg'; | |||||
| import { ReactComponent as RadioOff } from '../../assets/images/svg/radio-off.svg'; | |||||
| import { ReactComponent as RadioOn } from 'assets/images/svg/radio-on.svg'; | |||||
| import { ReactComponent as RadioOff } from 'assets/images/svg/radio-off.svg'; | |||||
| const Checkbox = ({ | const Checkbox = ({ | ||||
| className, | className, |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import React from "react"; | |||||
| import PropTypes from "prop-types"; | |||||
| import BaseInputField from './BaseInputField'; | |||||
| import BaseInputField from "./BaseInputField"; | |||||
| const Search = ({ | const Search = ({ | ||||
| field, | field, | ||||
| form, | form, | ||||
| label, | |||||
| placeholder, | placeholder, | ||||
| disabled, | disabled, | ||||
| className, | className, |
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import Select, { components, createFilter } from 'react-select'; | import Select, { components, createFilter } from 'react-select'; | ||||
| import { ErrorMessage, useField } from 'formik'; | import { ErrorMessage, useField } from 'formik'; | ||||
| import { ReactComponent as FilledChevronDown } from '../../assets/images/svg/filled-chevron-down.svg'; | |||||
| import { ReactComponent as FilledChevronDown } from 'assets/images/svg/filled-chevron-down.svg'; | |||||
| const SelectField = ({ | const SelectField = ({ | ||||
| label, | label, |
| TAB_KEYCODE, | TAB_KEYCODE, | ||||
| RIGHT_ARROW_KEYCODE, | RIGHT_ARROW_KEYCODE, | ||||
| LEFT_ARROW_KEYCODE, | LEFT_ARROW_KEYCODE, | ||||
| } from '../../constants/keyCodeConstants'; | |||||
| } from 'constants/keyCodeConstants'; | |||||
| const TextField = ({ | const TextField = ({ | ||||
| field, | field, |
| import React, { useEffect, useState } from 'react'; | |||||
| import React, { useEffect, useState } from "react"; | |||||
| import { | import { | ||||
| Paper, | |||||
| Box, | |||||
| Grid, | |||||
| Typography, | |||||
| Divider, | |||||
| TablePagination, | |||||
| TextField, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| } from '@mui/material'; | |||||
| Paper, | |||||
| Box, | |||||
| Grid, | |||||
| Typography, | |||||
| Divider, | |||||
| TablePagination, | |||||
| TextField, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| } from "@mui/material"; | |||||
| // import { useTranslation } from 'react-i18next'; | // import { useTranslation } from 'react-i18next'; | ||||
| import { useDispatch, useSelector, batch } from 'react-redux'; | |||||
| import useDebounce from '../../../hooks/useDebounceHook'; | |||||
| import { useDispatch, useSelector, batch } from "react-redux"; | |||||
| import useDebounce from "hooks/useDebounceHook"; | |||||
| import { | import { | ||||
| itemsSelector, | |||||
| pageSelector, | |||||
| itemsPerPageSelector, | |||||
| countSelector, | |||||
| sortSelector, | |||||
| } from '../../../store/selectors/randomDataSelectors'; | |||||
| itemsSelector, | |||||
| pageSelector, | |||||
| itemsPerPageSelector, | |||||
| countSelector, | |||||
| sortSelector, | |||||
| } from "store/selectors/randomDataSelectors"; | |||||
| import { | import { | ||||
| loadData, | |||||
| updatePage, | |||||
| updateItemsPerPage, | |||||
| updateFilter, | |||||
| updateSort, | |||||
| } from '../../../store/actions/randomData/randomDataActions'; | |||||
| loadData, | |||||
| updatePage, | |||||
| updateItemsPerPage, | |||||
| updateFilter, | |||||
| updateSort, | |||||
| } from "store/actions/randomData/randomDataActions"; | |||||
| const PagingSortingFilteringExample = () => { | const PagingSortingFilteringExample = () => { | ||||
| const [filterText, setFilterText] = useState(''); | |||||
| const [filterText, setFilterText] = useState(""); | |||||
| const dispatch = useDispatch(); | |||||
| // const { t } = useTranslation(); | |||||
| const items = useSelector(itemsSelector); | |||||
| const currentPage = useSelector(pageSelector); | |||||
| const itemsPerPage = useSelector(itemsPerPageSelector); | |||||
| const totalCount = useSelector(countSelector); | |||||
| const sort = useSelector(sortSelector) || 'name-asc'; | |||||
| const dispatch = useDispatch(); | |||||
| // const { t } = useTranslation(); | |||||
| const items = useSelector(itemsSelector); | |||||
| const currentPage = useSelector(pageSelector); | |||||
| const itemsPerPage = useSelector(itemsPerPageSelector); | |||||
| const totalCount = useSelector(countSelector); | |||||
| const sort = useSelector(sortSelector) || "name-asc"; | |||||
| // Use debounce to prevent too many rerenders | |||||
| const debouncedFilterText = useDebounce(filterText, 500); | |||||
| // Use debounce to prevent too many rerenders | |||||
| const debouncedFilterText = useDebounce(filterText, 500); | |||||
| useEffect(() => { | |||||
| dispatch(loadData(30)); | |||||
| dispatch(updateSort(sort)); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| dispatch(loadData(30)); | |||||
| dispatch(updateSort(sort)); | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| batch(() => { | |||||
| dispatch(updateFilter(filterText)); | |||||
| currentPage > 0 && dispatch(updatePage(0)); | |||||
| }); | |||||
| }, [debouncedFilterText]); | |||||
| useEffect(() => { | |||||
| batch(() => { | |||||
| dispatch(updateFilter(filterText)); | |||||
| currentPage > 0 && dispatch(updatePage(0)); | |||||
| }); | |||||
| }, [debouncedFilterText]); | |||||
| const handleFilterTextChange = (event) => { | |||||
| const filterText = event.target.value; | |||||
| setFilterText(filterText); | |||||
| }; | |||||
| const handleFilterTextChange = (event) => { | |||||
| const filterText = event.target.value; | |||||
| setFilterText(filterText); | |||||
| }; | |||||
| const handleSortChange = (event) => { | |||||
| const sort = event.target.value; | |||||
| dispatch(updateSort(sort)); | |||||
| }; | |||||
| const handleSortChange = (event) => { | |||||
| const sort = event.target.value; | |||||
| dispatch(updateSort(sort)); | |||||
| }; | |||||
| const handlePageChange = (event, newPage) => { | |||||
| dispatch(updatePage(newPage)); | |||||
| }; | |||||
| const handlePageChange = (event, newPage) => { | |||||
| dispatch(updatePage(newPage)); | |||||
| }; | |||||
| const handleItemsPerPageChange = (event) => { | |||||
| const itemsPerPage = parseInt(event.target.value); | |||||
| batch(() => { | |||||
| dispatch(updateItemsPerPage(itemsPerPage)); | |||||
| dispatch(updatePage(0)); | |||||
| }); | |||||
| }; | |||||
| const handleItemsPerPageChange = (event) => { | |||||
| const itemsPerPage = parseInt(event.target.value); | |||||
| batch(() => { | |||||
| dispatch(updateItemsPerPage(itemsPerPage)); | |||||
| dispatch(updatePage(0)); | |||||
| }); | |||||
| }; | |||||
| return ( | |||||
| <Paper | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| flexDirection: 'column', | |||||
| justifyContent: 'start', | |||||
| py: 2, | |||||
| minHeight: 500, | |||||
| }} | |||||
| elevation={5} | |||||
| > | |||||
| <Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center"> | |||||
| Pagination, Filtering and Sorting Example Client Side | |||||
| </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| flexWrap: 'wrap', | |||||
| mx: 2, | |||||
| }} | |||||
| > | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| width: '100%', | |||||
| }} | |||||
| > | |||||
| {/* TODO Separate into SelectComponent */} | |||||
| <FormControl sx={{ flexGrow: 1 }}> | |||||
| <InputLabel id="sort-label">Sort</InputLabel> | |||||
| <Select | |||||
| label="Sort" | |||||
| labelId="sort-label" | |||||
| id="sort-select-helper" | |||||
| value={sort} | |||||
| onChange={handleSortChange} | |||||
| > | |||||
| <MenuItem value="name-asc">Name - A-Z</MenuItem> | |||||
| <MenuItem value="name-desc">Name - Z-A</MenuItem> | |||||
| <MenuItem value="price-asc">Price - Lowest to Highest</MenuItem> | |||||
| <MenuItem value="price-desc">Price - Highest to Lowest</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| <TextField | |||||
| sx={{ flexGrow: 1 }} | |||||
| variant="outlined" | |||||
| label="Filter" | |||||
| placeholder="Filter" | |||||
| value={filterText} | |||||
| onChange={handleFilterTextChange} | |||||
| /> | |||||
| </Box> | |||||
| </Box> | |||||
| <Grid container> | |||||
| {items && | |||||
| items.length > 0 && | |||||
| items | |||||
| .slice( | |||||
| currentPage * itemsPerPage, | |||||
| currentPage * itemsPerPage + itemsPerPage | |||||
| ) | |||||
| .map((product, index) => ( | |||||
| // ! DON'T USE index for key, this is for example only | |||||
| <Grid item sx={{ p: 2 }} xs={12} sm={6} md={4} lg={3} key={index}> | |||||
| {/* TODO separate into component */} | |||||
| <Paper sx={{ p: 3, height: '100%' }} elevation={3}> | |||||
| <Typography sx={{ fontWeight: 600 }}>Name: </Typography> | |||||
| <Typography display="inline"> {product.name}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Designer: </Typography> | |||||
| <Typography display="inline"> {product.designer}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Type: </Typography> | |||||
| <Typography display="inline"> {product.type}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Price: </Typography> | |||||
| <Typography display="inline"> ${product.price}</Typography> | |||||
| </Paper> | |||||
| </Grid> | |||||
| ))} | |||||
| </Grid> | |||||
| <Box sx={{ width: '100%' }}> | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={totalCount} | |||||
| page={currentPage} | |||||
| onPageChange={handlePageChange} | |||||
| rowsPerPage={itemsPerPage} | |||||
| onRowsPerPageChange={handleItemsPerPageChange} | |||||
| rowsPerPageOptions={[12, 24, 48, 96]} | |||||
| labelRowsPerPage="Items per page" | |||||
| showFirstButton | |||||
| showLastButton | |||||
| /> | |||||
| </Box> | |||||
| </Paper> | |||||
| ); | |||||
| return ( | |||||
| <Paper | |||||
| sx={{ | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| justifyContent: "start", | |||||
| py: 2, | |||||
| minHeight: 500, | |||||
| }} | |||||
| elevation={5} | |||||
| > | |||||
| <Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center"> | |||||
| Pagination, Filtering and Sorting Example Client Side | |||||
| </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| flexWrap: "wrap", | |||||
| mx: 2, | |||||
| }} | |||||
| > | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| width: "100%", | |||||
| }} | |||||
| > | |||||
| {/* TODO Separate into SelectComponent */} | |||||
| <FormControl sx={{ flexGrow: 1 }}> | |||||
| <InputLabel id="sort-label">Sort</InputLabel> | |||||
| <Select | |||||
| label="Sort" | |||||
| labelId="sort-label" | |||||
| id="sort-select-helper" | |||||
| value={sort} | |||||
| onChange={handleSortChange} | |||||
| > | |||||
| <MenuItem value="name-asc">Name - A-Z</MenuItem> | |||||
| <MenuItem value="name-desc">Name - Z-A</MenuItem> | |||||
| <MenuItem value="price-asc">Price - Lowest to Highest</MenuItem> | |||||
| <MenuItem value="price-desc">Price - Highest to Lowest</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| <TextField | |||||
| sx={{ flexGrow: 1 }} | |||||
| variant="outlined" | |||||
| label="Filter" | |||||
| placeholder="Filter" | |||||
| value={filterText} | |||||
| onChange={handleFilterTextChange} | |||||
| /> | |||||
| </Box> | |||||
| </Box> | |||||
| <Grid container> | |||||
| {items && | |||||
| items.length > 0 && | |||||
| items | |||||
| .slice( | |||||
| currentPage * itemsPerPage, | |||||
| currentPage * itemsPerPage + itemsPerPage | |||||
| ) | |||||
| .map((product, index) => ( | |||||
| // ! DON'T USE index for key, this is for example only | |||||
| <Grid item sx={{ p: 2 }} xs={12} sm={6} md={4} lg={3} key={index}> | |||||
| {/* TODO separate into component */} | |||||
| <Paper sx={{ p: 3, height: "100%" }} elevation={3}> | |||||
| <Typography sx={{ fontWeight: 600 }}>Name: </Typography> | |||||
| <Typography display="inline"> {product.name}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Designer: </Typography> | |||||
| <Typography display="inline"> {product.designer}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Type: </Typography> | |||||
| <Typography display="inline"> {product.type}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Price: </Typography> | |||||
| <Typography display="inline"> ${product.price}</Typography> | |||||
| </Paper> | |||||
| </Grid> | |||||
| ))} | |||||
| </Grid> | |||||
| <Box sx={{ width: "100%" }}> | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={totalCount} | |||||
| page={currentPage} | |||||
| onPageChange={handlePageChange} | |||||
| rowsPerPage={itemsPerPage} | |||||
| onRowsPerPageChange={handleItemsPerPageChange} | |||||
| rowsPerPageOptions={[12, 24, 48, 96]} | |||||
| labelRowsPerPage="Items per page" | |||||
| showFirstButton | |||||
| showLastButton | |||||
| /> | |||||
| </Box> | |||||
| </Paper> | |||||
| ); | |||||
| }; | }; | ||||
| export default PagingSortingFilteringExample; | export default PagingSortingFilteringExample; |
| import React, { useEffect, useState } from 'react'; | |||||
| import React, { useEffect, useState } from "react"; | |||||
| import { | import { | ||||
| Paper, | |||||
| Box, | |||||
| Grid, | |||||
| Typography, | |||||
| Divider, | |||||
| TablePagination, | |||||
| TextField, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| } from '@mui/material'; | |||||
| Paper, | |||||
| Box, | |||||
| Grid, | |||||
| Typography, | |||||
| Divider, | |||||
| TablePagination, | |||||
| TextField, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| } from "@mui/material"; | |||||
| // import { useTranslation } from 'react-i18next'; | // import { useTranslation } from 'react-i18next'; | ||||
| import Backdrop from '../BackdropComponent'; | |||||
| import useDebounce from '../../../hooks/useDebounceHook'; | |||||
| import { useRandomData } from '../../../context/RandomDataContext'; | |||||
| import Backdrop from "../BackdropComponent"; | |||||
| import useDebounce from "hooks/useDebounceHook"; | |||||
| import { useRandomData } from "context/RandomDataContext"; | |||||
| const PagingSortingFilteringExampleServerSide = () => { | const PagingSortingFilteringExampleServerSide = () => { | ||||
| const [filterText, setFilterText] = useState(''); | |||||
| const { state, data } = useRandomData(); | |||||
| const { items, loading, totalCount, currentPage, itemsPerPage, sort } = data; | |||||
| const { setPage, setItemsPerPage, setSort, setFilter } = state; | |||||
| // const { t } = useTranslation(); | |||||
| const [filterText, setFilterText] = useState(""); | |||||
| const { state, data } = useRandomData(); | |||||
| const { items, loading, totalCount, currentPage, itemsPerPage, sort } = data; | |||||
| const { setPage, setItemsPerPage, setSort, setFilter } = state; | |||||
| // const { t } = useTranslation(); | |||||
| // Use debounce to prevent too many rerenders | |||||
| const debouncedFilterText = useDebounce(filterText, 500); | |||||
| // Use debounce to prevent too many rerenders | |||||
| const debouncedFilterText = useDebounce(filterText, 500); | |||||
| useEffect(() => { | |||||
| setFilter(filterText); | |||||
| }, [debouncedFilterText]); | |||||
| useEffect(() => { | |||||
| setFilter(filterText); | |||||
| }, [debouncedFilterText]); | |||||
| const handleFilterTextChange = (event) => { | |||||
| const filterText = event.target.value; | |||||
| setFilterText(filterText); | |||||
| }; | |||||
| const handleFilterTextChange = (event) => { | |||||
| const filterText = event.target.value; | |||||
| setFilterText(filterText); | |||||
| }; | |||||
| const handleSortChange = (event) => { | |||||
| const sort = event.target.value; | |||||
| setSort(sort); | |||||
| }; | |||||
| const handleSortChange = (event) => { | |||||
| const sort = event.target.value; | |||||
| setSort(sort); | |||||
| }; | |||||
| const handlePageChange = (event, newPage) => { | |||||
| setPage(newPage); | |||||
| }; | |||||
| const handlePageChange = (event, newPage) => { | |||||
| setPage(newPage); | |||||
| }; | |||||
| const handleItemsPerPageChange = (event) => { | |||||
| const itemsPerPage = parseInt(event.target.value); | |||||
| setItemsPerPage(itemsPerPage); | |||||
| setPage(0); | |||||
| }; | |||||
| const handleItemsPerPageChange = (event) => { | |||||
| const itemsPerPage = parseInt(event.target.value); | |||||
| setItemsPerPage(itemsPerPage); | |||||
| setPage(0); | |||||
| }; | |||||
| return ( | |||||
| <Paper | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| flexDirection: 'column', | |||||
| justifyContent: 'start', | |||||
| py: 2, | |||||
| minHeight: 500, | |||||
| position: 'relative', | |||||
| }} | |||||
| elevation={5} | |||||
| > | |||||
| {loading && <Backdrop isLoading position="absolute" />} | |||||
| <Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center"> | |||||
| Pagination, Filtering and Sorting Example Server Side | |||||
| </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| flexWrap: 'wrap', | |||||
| mx: 2, | |||||
| }} | |||||
| > | |||||
| <Box | |||||
| sx={{ | |||||
| display: 'flex', | |||||
| justifyContent: 'space-between', | |||||
| width: '100%', | |||||
| }} | |||||
| > | |||||
| <FormControl sx={{ flexGrow: 1 }}> | |||||
| <InputLabel id="sort-label">Sort</InputLabel> | |||||
| <Select | |||||
| label="Sort" | |||||
| labelId="sort-label" | |||||
| id="sort-select-helper" | |||||
| value={sort || ''} | |||||
| onChange={handleSortChange} | |||||
| > | |||||
| <MenuItem value="">None</MenuItem> | |||||
| <MenuItem value="name-asc">Name - A-Z</MenuItem> | |||||
| <MenuItem value="name-desc">Name - Z-A</MenuItem> | |||||
| <MenuItem value="price-asc">Price - Lowest to Highest</MenuItem> | |||||
| <MenuItem value="price-desc">Price - Highest to Lowest</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| <TextField | |||||
| sx={{ flexGrow: 1 }} | |||||
| variant="outlined" | |||||
| label="Filter" | |||||
| placeholder="Filter" | |||||
| value={filterText} | |||||
| onChange={handleFilterTextChange} | |||||
| /> | |||||
| </Box> | |||||
| <Grid container sx={{ position: 'relative' }}> | |||||
| {items && | |||||
| items.length > 0 && | |||||
| items.map((item) => ( | |||||
| <Grid | |||||
| item | |||||
| sx={{ p: 2 }} | |||||
| xs={12} | |||||
| sm={6} | |||||
| md={4} | |||||
| lg={3} | |||||
| key={item.id} | |||||
| > | |||||
| {/* TODO separate into component */} | |||||
| <Paper sx={{ p: 3, height: '100%' }} elevation={3}> | |||||
| <Typography sx={{ fontWeight: 600 }}>Name: </Typography> | |||||
| <Typography display="inline"> {item.name}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Company: </Typography> | |||||
| <Typography display="inline"> {item.company}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Color: </Typography> | |||||
| <Typography display="inline"> {item.color}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Price: </Typography> | |||||
| <Typography display="inline"> {item.price}</Typography> | |||||
| </Paper> | |||||
| </Grid> | |||||
| ))} | |||||
| </Grid> | |||||
| <Box sx={{ width: '100%' }}> | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={totalCount} | |||||
| page={currentPage} | |||||
| onPageChange={handlePageChange} | |||||
| rowsPerPage={itemsPerPage} | |||||
| onRowsPerPageChange={handleItemsPerPageChange} | |||||
| rowsPerPageOptions={[12, 24, 48, 96]} | |||||
| labelRowsPerPage="Items per page" | |||||
| showFirstButton | |||||
| showLastButton | |||||
| /> | |||||
| </Box> | |||||
| </Box> | |||||
| </Paper> | |||||
| ); | |||||
| return ( | |||||
| <Paper | |||||
| sx={{ | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| justifyContent: "start", | |||||
| py: 2, | |||||
| minHeight: 500, | |||||
| position: "relative", | |||||
| }} | |||||
| elevation={5} | |||||
| > | |||||
| {loading && <Backdrop isLoading position="absolute" />} | |||||
| <Typography sx={{ my: 4 }} variant="h4" gutterBottom align="center"> | |||||
| Pagination, Filtering and Sorting Example Server Side | |||||
| </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| flexWrap: "wrap", | |||||
| mx: 2, | |||||
| }} | |||||
| > | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| width: "100%", | |||||
| }} | |||||
| > | |||||
| <FormControl sx={{ flexGrow: 1 }}> | |||||
| <InputLabel id="sort-label">Sort</InputLabel> | |||||
| <Select | |||||
| label="Sort" | |||||
| labelId="sort-label" | |||||
| id="sort-select-helper" | |||||
| value={sort || ""} | |||||
| onChange={handleSortChange} | |||||
| > | |||||
| <MenuItem value="">None</MenuItem> | |||||
| <MenuItem value="name-asc">Name - A-Z</MenuItem> | |||||
| <MenuItem value="name-desc">Name - Z-A</MenuItem> | |||||
| <MenuItem value="price-asc">Price - Lowest to Highest</MenuItem> | |||||
| <MenuItem value="price-desc">Price - Highest to Lowest</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| <TextField | |||||
| sx={{ flexGrow: 1 }} | |||||
| variant="outlined" | |||||
| label="Filter" | |||||
| placeholder="Filter" | |||||
| value={filterText} | |||||
| onChange={handleFilterTextChange} | |||||
| /> | |||||
| </Box> | |||||
| <Grid container sx={{ position: "relative" }}> | |||||
| {items && | |||||
| items.length > 0 && | |||||
| items.map((item) => ( | |||||
| <Grid | |||||
| item | |||||
| sx={{ p: 2 }} | |||||
| xs={12} | |||||
| sm={6} | |||||
| md={4} | |||||
| lg={3} | |||||
| key={item.id} | |||||
| > | |||||
| {/* TODO separate into component */} | |||||
| <Paper sx={{ p: 3, height: "100%" }} elevation={3}> | |||||
| <Typography sx={{ fontWeight: 600 }}>Name: </Typography> | |||||
| <Typography display="inline"> {item.name}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Company: </Typography> | |||||
| <Typography display="inline"> {item.company}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Color: </Typography> | |||||
| <Typography display="inline"> {item.color}</Typography> | |||||
| <Divider /> | |||||
| <Typography sx={{ fontWeight: 600 }}>Price: </Typography> | |||||
| <Typography display="inline"> {item.price}</Typography> | |||||
| </Paper> | |||||
| </Grid> | |||||
| ))} | |||||
| </Grid> | |||||
| <Box sx={{ width: "100%" }}> | |||||
| <TablePagination | |||||
| component="div" | |||||
| count={totalCount} | |||||
| page={currentPage} | |||||
| onPageChange={handlePageChange} | |||||
| rowsPerPage={itemsPerPage} | |||||
| onRowsPerPageChange={handleItemsPerPageChange} | |||||
| rowsPerPageOptions={[12, 24, 48, 96]} | |||||
| labelRowsPerPage="Items per page" | |||||
| showFirstButton | |||||
| showLastButton | |||||
| /> | |||||
| </Box> | |||||
| </Box> | |||||
| </Paper> | |||||
| ); | |||||
| }; | }; | ||||
| export default PagingSortingFilteringExampleServerSide; | export default PagingSortingFilteringExampleServerSide; |
| import Brightness7Icon from "@mui/icons-material/Brightness7"; | import Brightness7Icon from "@mui/icons-material/Brightness7"; | ||||
| import MenuList from "./MenuListComponent"; | import MenuList from "./MenuListComponent"; | ||||
| import Drawer from "./DrawerComponent"; | import Drawer from "./DrawerComponent"; | ||||
| import { ColorModeContext } from "../../context/ColorModeContext"; | |||||
| import { ColorModeContext } from "context/ColorModeContext"; | |||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { logoutUser } from "../../store/actions/login/loginActions"; | |||||
| import { logoutUser } from "store/actions/login/loginActions"; | |||||
| const NavbarComponent = () => { | const NavbarComponent = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); |
| import React, { useEffect } from 'react'; | |||||
| import { Redirect, Route } from 'react-router'; | |||||
| import { useDispatch } from 'react-redux'; | |||||
| import { authenticateUser } from '../../store/actions/login/loginActions'; | |||||
| // import { selectIsUserAuthenticated } from '../../store/selectors/userSelectors'; | |||||
| import { LOGIN_PAGE } from '../../constants/pages'; | |||||
| import React, { useEffect } from "react"; | |||||
| import { Redirect, Route } from "react-router"; | |||||
| import { useDispatch } from "react-redux"; | |||||
| import { authenticateUser } from "store/actions/login/loginActions"; | |||||
| import { LOGIN_PAGE } from "constants/pages"; | |||||
| const PrivateRoute = ({ ...props }) => { | const PrivateRoute = ({ ...props }) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); |
| import React, { createContext } from 'react'; | import React, { createContext } from 'react'; | ||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import { ThemeProvider } from '@mui/material/styles'; | import { ThemeProvider } from '@mui/material/styles'; | ||||
| import useToggleColorMode from '../hooks/useToggleColorMode'; | |||||
| import useToggleColorMode from 'hooks/useToggleColorMode'; | |||||
| export const ColorModeContext = createContext(); | export const ColorModeContext = createContext(); | ||||
| import React, { createContext, useContext, useState } from 'react'; | import React, { createContext, useContext, useState } from 'react'; | ||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
| import usePagingHook from '../hooks/usePagingHook'; | |||||
| import { getRequest } from '../request/jsonServerRequest'; | |||||
| import usePagingHook from 'hooks/usePagingHook'; | |||||
| import { getRequest } from 'request/jsonServerRequest'; | |||||
| const apiCall = (page, itemsPerPage, sort, sortDirection, filter) => | const apiCall = (page, itemsPerPage, sort, sortDirection, filter) => | ||||
| getRequest('/items', { | getRequest('/items', { |
| import { | import { | ||||
| authScopeSetHelper, | authScopeSetHelper, | ||||
| authScopeStringGetHelper, | authScopeStringGetHelper, | ||||
| } from "../util/helpers/authScopeHelpers"; | |||||
| import selectedTheme from "../themes"; | |||||
| } from "util/helpers/authScopeHelpers"; | |||||
| import selectedTheme from "themes"; | |||||
| const useToggleColorMode = () => { | const useToggleColorMode = () => { | ||||
| const currentColorMode = authScopeStringGetHelper("colorMode") || "light"; | const currentColorMode = authScopeStringGetHelper("colorMode") || "light"; |
| import React from 'react'; | |||||
| import ReactDOM from 'react-dom'; | |||||
| import { Provider } from 'react-redux'; | |||||
| import { HelmetProvider } from 'react-helmet-async'; | |||||
| import React from "react"; | |||||
| import ReactDOM from "react-dom"; | |||||
| import { Provider } from "react-redux"; | |||||
| import { HelmetProvider } from "react-helmet-async"; | |||||
| import './main.scss'; | |||||
| import App from './App'; | |||||
| import store from './store'; | |||||
| import "./main.scss"; | |||||
| import App from "./App"; | |||||
| import store from "./store"; | |||||
| import './i18n'; | |||||
| import ColorModeProvider from './context/ColorModeContext'; | |||||
| import "./i18n"; | |||||
| import ColorModeProvider from "./context/ColorModeContext"; | |||||
| import { CssBaseline } from "@mui/material"; | |||||
| ReactDOM.render( | ReactDOM.render( | ||||
| <HelmetProvider> | <HelmetProvider> | ||||
| <React.StrictMode> | <React.StrictMode> | ||||
| <Provider store={store}> | <Provider store={store}> | ||||
| <ColorModeProvider> | <ColorModeProvider> | ||||
| <App /> | |||||
| <CssBaseline /> | |||||
| <App /> | |||||
| </ColorModeProvider> | </ColorModeProvider> | ||||
| </Provider> | </Provider> | ||||
| </React.StrictMode> | </React.StrictMode> | ||||
| </HelmetProvider>, | </HelmetProvider>, | ||||
| document.getElementById('root'), | |||||
| document.getElementById("root") | |||||
| ); | ); |
| import React, { useEffect } from "react"; | import React, { useEffect } from "react"; | ||||
| import { useLocation } from "react-router-dom"; | import { useLocation } from "react-router-dom"; | ||||
| import { HOME_PAGE } from "../../constants/pages"; | |||||
| import { HOME_PAGE } from "constants/pages"; | |||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { useParams } from "react-router-dom"; | import { useParams } from "react-router-dom"; | ||||
| import { useDispatch, useSelector } from "react-redux"; | import { useDispatch, useSelector } from "react-redux"; | ||||
| import { fetchAuthProvider } from "../../store/actions/authProvider/authProviderActions"; | |||||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||||
| import { AUTH_PROVIDER_SCOPE } from "../../store/actions/authProvider/authProviderActionConstants"; | |||||
| import Backdrop from '../../components/MUI/BackdropComponent'; | |||||
| import { fetchAuthProvider } from "store/actions/authProvider/authProviderActions"; | |||||
| import { selectIsLoadingByActionType } from "store/selectors/loadingSelectors"; | |||||
| import { AUTH_PROVIDER_SCOPE } from "store/actions/authProvider/authProviderActionConstants"; | |||||
| import Backdrop from 'components/MUI/BackdropComponent'; | |||||
| function AuthCallback({ history }) { | function AuthCallback({ history }) { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); |
| import React from 'react'; | import React from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import Button from '../../components/Button/Button'; | |||||
| import Section from '../../components/Section/Section'; | |||||
| import Button from 'components/Button/Button'; | |||||
| import Section from 'components/Section/Section'; | |||||
| const NotFoundPage = () => { | const NotFoundPage = () => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); |
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import * as Yup from 'yup'; | import * as Yup from 'yup'; | ||||
| import i18next from 'i18next'; | import i18next from 'i18next'; | ||||
| import Auth from '../../components/Auth/Auth'; | |||||
| import AuthCard from '../../components/AuthCards/AuthCard'; | |||||
| import TextField from '../../components/InputFields/TextField'; | |||||
| import Button from '../../components/Button/Button'; | |||||
| import Auth from 'components/Auth/Auth'; | |||||
| import AuthCard from 'components/AuthCards/AuthCard'; | |||||
| import TextField from 'components/InputFields/TextField'; | |||||
| import Button from 'components/Button/Button'; | |||||
| import Section from '../../components/Section/Section'; | |||||
| import Section from 'components/Section/Section'; | |||||
| const forgotPasswordValidationSchema = Yup.object().shape({ | const forgotPasswordValidationSchema = Yup.object().shape({ | ||||
| email: Yup.string().required( | email: Yup.string().required( |
| import React from 'react'; | |||||
| import { useFormik } from 'formik'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import React from "react"; | |||||
| import { useFormik } from "formik"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { | import { | ||||
| Box, | |||||
| Container, | |||||
| Typography, | |||||
| Button, | |||||
| TextField, | |||||
| Link, | |||||
| Grid, | |||||
| } from '@mui/material'; | |||||
| import Backdrop from '../../components/MUI/BackdropComponent'; | |||||
| import { LOGIN_PAGE } from '../../constants/pages'; | |||||
| import { NavLink } from 'react-router-dom'; | |||||
| import forgotPasswordValidation from '../../validations/forgotPasswordValidation'; | |||||
| import forgotPasswordInitialValues from '../../initialValues/forgotPasswordInitialValues'; | |||||
| Box, | |||||
| Container, | |||||
| Typography, | |||||
| Button, | |||||
| TextField, | |||||
| Link, | |||||
| Grid, | |||||
| } from "@mui/material"; | |||||
| import Backdrop from "components/MUI/BackdropComponent"; | |||||
| import { LOGIN_PAGE } from "constants/pages"; | |||||
| import { NavLink } from "react-router-dom"; | |||||
| import forgotPasswordValidation from "validations/forgotPasswordValidation"; | |||||
| import forgotPasswordInitialValues from "initialValues/forgotPasswordInitialValues"; | |||||
| const ForgotPasswordPage = () => { | const ForgotPasswordPage = () => { | ||||
| const { t } = useTranslation(); | |||||
| const { t } = useTranslation(); | |||||
| const handleSubmit = (values) => { | |||||
| console.log('Values', values); | |||||
| }; | |||||
| const handleSubmit = (values) => { | |||||
| console.log("Values", values); | |||||
| }; | |||||
| const formik = useFormik({ | |||||
| initialValues: forgotPasswordInitialValues, | |||||
| validationSchema: forgotPasswordValidation, | |||||
| onSubmit: handleSubmit, | |||||
| validateOnBlur: true, | |||||
| enableReinitialize: true, | |||||
| }); | |||||
| const formik = useFormik({ | |||||
| initialValues: forgotPasswordInitialValues, | |||||
| validationSchema: forgotPasswordValidation, | |||||
| onSubmit: handleSubmit, | |||||
| validateOnBlur: true, | |||||
| enableReinitialize: true, | |||||
| }); | |||||
| return ( | |||||
| <Container component="main" maxWidth="md"> | |||||
| <Box | |||||
| sx={{ | |||||
| marginTop: 32, | |||||
| display: 'flex', | |||||
| flexDirection: 'column', | |||||
| alignItems: 'center', | |||||
| }} | |||||
| > | |||||
| <Typography component="h1" variant="h5"> | |||||
| {t('forgotPassword.title')} | |||||
| </Typography> | |||||
| <Box | |||||
| component="form" | |||||
| onSubmit={formik.handleSubmit} | |||||
| sx={{ position: 'relative', mt: 1, p: 1 }} | |||||
| > | |||||
| <Backdrop position="absolute" isLoading={false} /> | |||||
| <TextField | |||||
| name="email" | |||||
| label={t('login.forgotPasswordEmail')} | |||||
| margin="normal" | |||||
| value={formik.values.email} | |||||
| onChange={formik.handleChange} | |||||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||||
| helperText={formik.touched.email && formik.errors.email} | |||||
| autoFocus | |||||
| fullWidth | |||||
| /> | |||||
| <Button | |||||
| type="submit" | |||||
| variant="contained" | |||||
| sx={{ mt: 3, mb: 2 }} | |||||
| fullWidth | |||||
| > | |||||
| {t('forgotPassword.label')} | |||||
| </Button> | |||||
| <Grid container justifyContent="center"> | |||||
| <Link | |||||
| to={LOGIN_PAGE} | |||||
| component={NavLink} | |||||
| variant="body2" | |||||
| underline="hover" | |||||
| > | |||||
| {t('common.back')} | |||||
| </Link> | |||||
| </Grid> | |||||
| </Box> | |||||
| </Box> | |||||
| </Container> | |||||
| ); | |||||
| return ( | |||||
| <Container component="main" maxWidth="md"> | |||||
| <Box | |||||
| sx={{ | |||||
| marginTop: 32, | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| alignItems: "center", | |||||
| }} | |||||
| > | |||||
| <Typography component="h1" variant="h5"> | |||||
| {t("forgotPassword.title")} | |||||
| </Typography> | |||||
| <Box | |||||
| component="form" | |||||
| onSubmit={formik.handleSubmit} | |||||
| sx={{ position: "relative", mt: 1, p: 1 }} | |||||
| > | |||||
| <Backdrop position="absolute" isLoading={false} /> | |||||
| <TextField | |||||
| name="email" | |||||
| label={t("login.forgotPasswordEmail")} | |||||
| margin="normal" | |||||
| value={formik.values.email} | |||||
| onChange={formik.handleChange} | |||||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||||
| helperText={formik.touched.email && formik.errors.email} | |||||
| autoFocus | |||||
| fullWidth | |||||
| /> | |||||
| <Button | |||||
| type="submit" | |||||
| variant="contained" | |||||
| sx={{ mt: 3, mb: 2 }} | |||||
| fullWidth | |||||
| > | |||||
| {t("forgotPassword.label")} | |||||
| </Button> | |||||
| <Grid container justifyContent="center"> | |||||
| <Link | |||||
| to={LOGIN_PAGE} | |||||
| component={NavLink} | |||||
| variant="body2" | |||||
| underline="hover" | |||||
| > | |||||
| {t("common.back")} | |||||
| </Link> | |||||
| </Grid> | |||||
| </Box> | |||||
| </Box> | |||||
| </Container> | |||||
| ); | |||||
| }; | }; | ||||
| export default ForgotPasswordPage; | export default ForgotPasswordPage; |
| import React from "react"; | import React from "react"; | ||||
| import { Box, Button, Grid } from "@mui/material"; | import { Box, Button, 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 { getRequest } from "../../request"; | |||||
| 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 { getRequest } from "request"; | |||||
| const HomePage = () => { | const HomePage = () => { | ||||
| const getPosts = async () => { | const getPosts = async () => { |
| import React from 'react'; | |||||
| import PropTypes from 'prop-types'; | |||||
| import { Field, Form, Formik } from 'formik'; | |||||
| import { useDispatch, useSelector } from 'react-redux'; | |||||
| import { NavLink } from 'react-router-dom'; | |||||
| import * as Yup from 'yup'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import i18next from 'i18next'; | |||||
| import PasswordField from '../../components/InputFields/PasswordField'; | |||||
| import Button from '../../components/Button/Button'; | |||||
| import TextField from '../../components/InputFields/TextField'; | |||||
| import Auth from '../../components/Auth/Auth'; | |||||
| import AuthCard from '../../components/AuthCards/AuthCard'; | |||||
| import { | |||||
| clearLoginErrors, | |||||
| fetchUser, | |||||
| } from '../../store/actions/login/loginActions'; | |||||
| import { | |||||
| selectLoginError, | |||||
| } from '../../store/selectors/loginSelectors'; | |||||
| import { | |||||
| FORGOT_PASSWORD_PAGE, HOME_PAGE, | |||||
| } from '../../constants/pages'; | |||||
| import { selectIsLoadingByActionType } from '../../store/selectors/loadingSelectors'; | |||||
| import { LOGIN_USER_LOADING } from '../../store/actions/login/loginActionConstants'; | |||||
| import React from "react"; | |||||
| import PropTypes from "prop-types"; | |||||
| import { Field, Form, Formik } from "formik"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | |||||
| import { NavLink } from "react-router-dom"; | |||||
| import * as Yup from "yup"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import i18next from "i18next"; | |||||
| import PasswordField from "components/InputFields/PasswordField"; | |||||
| import Button from "components/Button/Button"; | |||||
| import TextField from "components/InputFields/TextField"; | |||||
| import Auth from "components/Auth/Auth"; | |||||
| import AuthCard from "components/AuthCards/AuthCard"; | |||||
| import { clearLoginErrors, fetchUser } from "store/actions/login/loginActions"; | |||||
| import { selectLoginError } from "store/selectors/loginSelectors"; | |||||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "constants/pages"; | |||||
| import { selectIsLoadingByActionType } from "store/selectors/loadingSelectors"; | |||||
| import { LOGIN_USER_LOADING } from "store/actions/login/loginActionConstants"; | |||||
| const LoginValidationSchema = Yup.object().shape({ | const LoginValidationSchema = Yup.object().shape({ | ||||
| username: Yup.string().required(i18next.t('login.usernameRequired')), | |||||
| password: Yup.string().required(i18next.t('login.passwordRequired')), | |||||
| username: Yup.string().required(i18next.t("login.usernameRequired")), | |||||
| password: Yup.string().required(i18next.t("login.passwordRequired")), | |||||
| }); | }); | ||||
| const LoginPage = ({ history }) => { | const LoginPage = ({ history }) => { | ||||
| // }, [history, tokens]); | // }, [history, tokens]); | ||||
| const isLoading = useSelector( | const isLoading = useSelector( | ||||
| selectIsLoadingByActionType(LOGIN_USER_LOADING), | |||||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | |||||
| ); | ); | ||||
| const handleApiResponseSuccess =()=>{ | |||||
| const handleApiResponseSuccess = () => { | |||||
| history.push({ | history.push({ | ||||
| pathname: HOME_PAGE, | pathname: HOME_PAGE, | ||||
| state: { | state: { | ||||
| from: history.location.pathname, | from: history.location.pathname, | ||||
| }, | }, | ||||
| }); | }); | ||||
| } | |||||
| }; | |||||
| const handleSubmit = (values) => { | const handleSubmit = (values) => { | ||||
| // destructure value as username. | |||||
| const { username: Username } = values; | |||||
| const { password: Password } = values; | |||||
| dispatch(clearLoginErrors()); | |||||
| dispatch( | |||||
| fetchUser({ | |||||
| Username, | |||||
| Password, | |||||
| handleApiResponseSuccess | |||||
| }, | |||||
| ), | |||||
| ); | |||||
| // destructure value as username. | |||||
| const { username: Username } = values; | |||||
| const { password: Password } = values; | |||||
| dispatch(clearLoginErrors()); | |||||
| dispatch( | |||||
| fetchUser({ | |||||
| Username, | |||||
| Password, | |||||
| handleApiResponseSuccess, | |||||
| }) | |||||
| ); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <Auth> | <Auth> | ||||
| <AuthCard | |||||
| title="Log In" | |||||
| isLoading={isLoading} | |||||
| > | |||||
| <AuthCard title="Log In" isLoading={isLoading}> | |||||
| <div className="c-login c-login--user"> | <div className="c-login c-login--user"> | ||||
| <div className="c-login__form"> | <div className="c-login__form"> | ||||
| <Formik | <Formik | ||||
| initialValues={{ | initialValues={{ | ||||
| username: '', | |||||
| password: '', | |||||
| username: "", | |||||
| password: "", | |||||
| }} | }} | ||||
| onSubmit={handleSubmit} | onSubmit={handleSubmit} | ||||
| validationSchema={LoginValidationSchema} | validationSchema={LoginValidationSchema} | ||||
| {({ values }) => ( | {({ values }) => ( | ||||
| <Form> | <Form> | ||||
| <Field | <Field | ||||
| label={t('common.labelUsername')} | |||||
| label={t("common.labelUsername")} | |||||
| value={values.username.value} | value={values.username.value} | ||||
| component={TextField} | component={TextField} | ||||
| name="username" | name="username" | ||||
| <Field | <Field | ||||
| label={ | label={ | ||||
| <div className="c-login--password__label"> | <div className="c-login--password__label"> | ||||
| {t('common.labelPassword')} | |||||
| {t("common.labelPassword")} | |||||
| </div> | </div> | ||||
| } | } | ||||
| link={ | link={ | ||||
| <NavLink | |||||
| to={FORGOT_PASSWORD_PAGE} | |||||
| > | |||||
| {t('login.forgotYourPassword')} | |||||
| </NavLink> | |||||
| <NavLink to={FORGOT_PASSWORD_PAGE}> | |||||
| {t("login.forgotYourPassword")} | |||||
| </NavLink> | |||||
| } | } | ||||
| name="password" | name="password" | ||||
| component={PasswordField} | component={PasswordField} | ||||
| variant="primary" | variant="primary" | ||||
| type="submit" | type="submit" | ||||
| > | > | ||||
| {t('common.continue')} | |||||
| {t("common.continue")} | |||||
| </Button> | </Button> | ||||
| </Form> | </Form> | ||||
| )} | )} | ||||
| </Formik> | </Formik> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </AuthCard> | |||||
| </Auth> | |||||
| </AuthCard> | |||||
| </Auth> | |||||
| ); | ); | ||||
| }; | }; | ||||
| 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 { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { clearLoginErrors, fetchUser } from "store/actions/login/loginActions"; | |||||
| import { selectLoginError } from "store/selectors/loginSelectors"; | |||||
| import { | import { | ||||
| clearLoginErrors, | |||||
| fetchUser, | |||||
| } from "../../store/actions/login/loginActions"; | |||||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE, REGISTER_PAGE } from "../../constants/pages"; | |||||
| FORGOT_PASSWORD_PAGE, | |||||
| HOME_PAGE, | |||||
| REGISTER_PAGE, | |||||
| } from "constants/pages"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| Typography, | Typography, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { Visibility, VisibilityOff } from "@mui/icons-material"; | import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||||
| import ErrorMessage from "../../components/MUI/ErrorMessageComponent"; | |||||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||||
| import { LOGIN_USER_SCOPE } from "../../store/actions/login/loginActionConstants"; | |||||
| import loginValidation from "../../validations/loginValidation"; | |||||
| import loginInitialValues from "../../initialValues/loginInitialValues"; | |||||
| import Backdrop from "components/MUI/BackdropComponent"; | |||||
| import ErrorMessage from "components/MUI/ErrorMessageComponent"; | |||||
| import { selectIsLoadingByActionType } from "store/selectors/loadingSelectors"; | |||||
| import { LOGIN_USER_SCOPE } from "store/actions/login/loginActionConstants"; | |||||
| import loginValidation from "validations/loginValidation"; | |||||
| import loginInitialValues from "initialValues/loginInitialValues"; | |||||
| import GoogleIcon from "@mui/icons-material/Google"; | import GoogleIcon from "@mui/icons-material/Google"; | ||||
| const LoginPage = ({ history }) => { | const LoginPage = ({ history }) => { |
| import { | import { | ||||
| clearRegisterErrors, | clearRegisterErrors, | ||||
| registerUser, | registerUser, | ||||
| } from "../../store/actions/register/registerActions"; | |||||
| import { selectRegisterError } from "../../store/selectors/registerSelectors"; | |||||
| import { | |||||
| FORGOT_PASSWORD_PAGE, | |||||
| HOME_PAGE, | |||||
| LOGIN_PAGE, | |||||
| } from "../../constants/pages"; | |||||
| } from "store/actions/register/registerActions"; | |||||
| import { selectRegisterError } from "store/selectors/registerSelectors"; | |||||
| import { FORGOT_PASSWORD_PAGE, LOGIN_PAGE } from "constants/pages"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| Typography, | Typography, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { Visibility, VisibilityOff } from "@mui/icons-material"; | import { Visibility, VisibilityOff } from "@mui/icons-material"; | ||||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||||
| import ErrorMessage from "../../components/MUI/ErrorMessageComponent"; | |||||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||||
| import { LOGIN_USER_SCOPE } from "../../store/actions/login/loginActionConstants"; | |||||
| import Backdrop from "components/MUI/BackdropComponent"; | |||||
| import ErrorMessage from "components/MUI/ErrorMessageComponent"; | |||||
| import { selectIsLoadingByActionType } from "store/selectors/loadingSelectors"; | |||||
| import GoogleIcon from "@mui/icons-material/Google"; | import GoogleIcon from "@mui/icons-material/Google"; | ||||
| import registerInitialValues from "../../initialValues/registerInitialValues"; | |||||
| import registerValidation from "../../validations/registerValidation"; | |||||
| import { REGISTER_USER_SCOPE } from "../../store/actions/register/registerActionConstants"; | |||||
| import { makeToastMessage } from "../../util/helpers/toastMessage"; | |||||
| import registerInitialValues from "initialValues/registerInitialValues"; | |||||
| import registerValidation from "validations/registerValidation"; | |||||
| import { REGISTER_USER_SCOPE } from "store/actions/register/registerActionConstants"; | |||||
| import { makeToastMessage } from "util/helpers/toastMessage"; | |||||
| const RegisterPage = ({ history }) => { | const RegisterPage = ({ history }) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); |
| REFRESH_TOKEN_CONST, | REFRESH_TOKEN_CONST, | ||||
| } from "../../constants/localStorage"; | } from "../../constants/localStorage"; | ||||
| import { | import { | ||||
| authScopeClearHelper, | |||||
| authScopeStringGetHelper, | authScopeStringGetHelper, | ||||
| authScopeRemoveHelper, | authScopeRemoveHelper, | ||||
| authScopeSetHelper, | authScopeSetHelper, | ||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error); // eslint-disable-line | console.log(error); // eslint-disable-line | ||||
| } finally { | } finally { | ||||
| yield call(authScopeClearHelper); | |||||
| // yield call(authScopeClearHelper); | |||||
| yield call(authScopeRemoveHelper, JWT_TOKEN); | |||||
| yield call(authScopeRemoveHelper, JWT_REFRESH_TOKEN); | |||||
| yield call(removeHeaderToken); | yield call(removeHeaderToken); | ||||
| yield put(resetLoginState()); | yield put(resetLoginState()); | ||||
| yield call(history.replace, LOGIN_PAGE); | yield call(history.replace, LOGIN_PAGE); |
| import { SESSION_STORAGE_SCOPE } from '../../constants/sessionStorage'; | |||||
| import { SESSION_STORAGE_SCOPE } from 'constants/sessionStorage'; | |||||
| export function authScopeGetHelper(key) { | export function authScopeGetHelper(key) { | ||||
| if (sessionStorage.getItem(SESSION_STORAGE_SCOPE)) { | if (sessionStorage.getItem(SESSION_STORAGE_SCOPE)) { |