| @@ -9,19 +9,21 @@ import { StyledEngineProvider } from "@mui/material"; | |||
| const App = () => { | |||
| return ( | |||
| <> | |||
| <Router history={history}> | |||
| <Helmet> | |||
| <title>{i18next.t("app.title")}</title> | |||
| </Helmet> | |||
| {/* <main className="l-page"> */} | |||
| <> | |||
| <Router history={history}> | |||
| <Helmet> | |||
| <title>{i18next.t("app.title")}</title> | |||
| </Helmet> | |||
| {/* <main className="l-page"> */} | |||
| <StyledEngineProvider injectFirst> | |||
| <GlobalStyle /> | |||
| <AppRoutes /> | |||
| </StyledEngineProvider> | |||
| {/* </main> */} | |||
| </Router> | |||
| </> | |||
| )}; | |||
| {/* </main> */} | |||
| </Router> | |||
| </> | |||
| ); | |||
| }; | |||
| export default App; | |||
| @@ -14,11 +14,11 @@ import { | |||
| RESET_PASSWORD_PAGE, | |||
| CREATE_OFFER_PAGE, | |||
| } from './constants/pages'; | |||
| import LoginPage from './pages/LoginPage/LoginPageMUI'; | |||
| import LoginPage from './pages/LoginPage/LoginPage'; | |||
| import HomePage from './pages/HomePage/HomePageMUI'; | |||
| import NotFoundPage from './pages/ErrorPages/NotFoundPage'; | |||
| import ErrorPage from './pages/ErrorPages/ErrorPage'; | |||
| import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPageMUI'; | |||
| import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPage'; | |||
| import PrivateRoute from './components/Router/PrivateRoute'; | |||
| import MailSent from './pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent'; | |||
| import Register from './pages/RegisterPages/Register/Register'; | |||
| @@ -29,12 +29,14 @@ import selectedTheme from "../../../themes"; | |||
| import StepProgress from "../../StepProgress/StepProgress"; | |||
| import { Label } from "../../CheckBox/Label"; | |||
| import FirstPartCreateOffer from "./FirstPart/FirstPartCreateOffer"; | |||
| import SecondPartCreateOffer from "./SecondPart/SecondPartCreateOffer"; | |||
| const CreateOffer = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const [informations, setInformations] = useState({}); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const [currentStep, setCurrentStep] = useState(1); | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
| @@ -73,14 +75,20 @@ const CreateOffer = ({ history }) => { | |||
| ); | |||
| }; | |||
| const handleNext = (values) => { | |||
| setInformations({...values}); | |||
| setCurrentStep(prevState => prevState+1) | |||
| } | |||
| return ( | |||
| <CreateOfferContainer> | |||
| <CreateOfferTitle component="h1" variant="h5"> | |||
| Nova Objava | |||
| </CreateOfferTitle> | |||
| <StepProgress current={1} numberOfSteps={3} /> | |||
| <FirstPartCreateOffer /> | |||
| <StepProgress current={currentStep} numberOfSteps={3} /> | |||
| {currentStep === 1 && <FirstPartCreateOffer handleNext={handleNext}/>} | |||
| {currentStep === 2 && <SecondPartCreateOffer handleNext={handleNext} />} | |||
| </CreateOfferContainer> | |||
| ); | |||
| @@ -72,5 +72,5 @@ export const FieldLabel = styled(Label)` | |||
| export const SelectField = styled(Select)` | |||
| position: relative; | |||
| top: 15px; | |||
| margin-bottom: 30px; | |||
| margin-bottom: 18px; | |||
| ` | |||
| @@ -3,20 +3,24 @@ import PropTypes from "prop-types"; | |||
| import { useFormik } from "formik"; | |||
| import { | |||
| CreateOfferFormContainer, | |||
| DescriptionField, | |||
| FieldLabel, | |||
| NextButton, | |||
| TitleField, | |||
| } from "./FirstPartCreateOffer.styled"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| // import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| // import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import * as Yup from "yup"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import Option from "../../../Select/Option/Option"; | |||
| import { SelectField } from "../CreateOffer.styled"; | |||
| const FirstPartCreateOffer = () => { | |||
| const FirstPartCreateOffer = (props) => { | |||
| const { t } = useTranslation(); | |||
| const handleSubmit = (values) => { | |||
| console.log(values); | |||
| props.handleNext(values); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| @@ -36,7 +40,7 @@ const FirstPartCreateOffer = () => { | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <FieldLabel leftText={"NASLOV"} /> | |||
| <TextField | |||
| <TitleField | |||
| name="nameOfProduct" | |||
| placeholder={"Naziv proizvoda..."} | |||
| italicPlaceholder | |||
| @@ -50,7 +54,7 @@ const FirstPartCreateOffer = () => { | |||
| /> | |||
| <FieldLabel leftText={"OPIS PROIZVODA"} /> | |||
| <TextField | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={"Opis..."} | |||
| margin="normal" | |||
| @@ -65,7 +69,7 @@ const FirstPartCreateOffer = () => { | |||
| height={"100px"} | |||
| /> | |||
| <FieldLabel leftText={"KATEGORIJA"} /> | |||
| <FieldLabel leftText={"LOKACIJA"} /> | |||
| <SelectField defaultValue={1}> | |||
| <Option value={1}>Opcija 1</Option> | |||
| <Option value={2}>Opcija 2</Option> | |||
| @@ -73,7 +77,7 @@ const FirstPartCreateOffer = () => { | |||
| <Option value={4}>Opcija 4</Option> | |||
| </SelectField> | |||
| <FieldLabel leftText={"PODKATEGORIJA"} /> | |||
| <FieldLabel leftText={"KATEGORIJA"} /> | |||
| <SelectField defaultValue={1}> | |||
| <Option value={1}>Opcija 1</Option> | |||
| <Option value={2}>Opcija 2</Option> | |||
| @@ -81,7 +85,7 @@ const FirstPartCreateOffer = () => { | |||
| <Option value={4}>Opcija 4</Option> | |||
| </SelectField> | |||
| <FieldLabel leftText={"LOKACIJA"} /> | |||
| <FieldLabel leftText={"PODKATEGORIJA"} /> | |||
| <SelectField defaultValue={1}> | |||
| <Option value={1}>Opcija 1</Option> | |||
| <Option value={2}>Opcija 2</Option> | |||
| @@ -89,11 +93,11 @@ const FirstPartCreateOffer = () => { | |||
| <Option value={4}>Opcija 4</Option> | |||
| </SelectField> | |||
| <PrimaryButton | |||
| <NextButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| // disabled={ | |||
| @@ -102,13 +106,14 @@ const FirstPartCreateOffer = () => { | |||
| // } | |||
| > | |||
| NASTAVI | |||
| </PrimaryButton> | |||
| </NextButton> | |||
| </CreateOfferFormContainer> | |||
| ); | |||
| }; | |||
| FirstPartCreateOffer.propTypes = { | |||
| children: PropTypes.any, | |||
| handleNext: PropTypes.func, | |||
| }; | |||
| export default FirstPartCreateOffer; | |||
| @@ -1,7 +1,9 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Label } from "../../../CheckBox/Label"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| export const CreateOfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -30,7 +32,7 @@ export const CreateOfferDescription = styled(Typography)` | |||
| align-items: center; | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| margin-bottom: 18px; | |||
| `; | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| @@ -48,4 +50,13 @@ export const FieldLabel = styled(Label)` | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| ` | |||
| export const DescriptionField = styled(TextField)` | |||
| margin-bottom: 4px; | |||
| ` | |||
| export const TitleField = styled(TextField)` | |||
| ` | |||
| export const NextButton = styled(PrimaryButton)` | |||
| margin-top: 16px; | |||
| ` | |||
| @@ -0,0 +1,14 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { CreateOfferFormContainer } from "./SecondPartCreateOffer.styled"; | |||
| const SecondPartCreateOffer = () => { | |||
| return <CreateOfferFormContainer>Aaaa</CreateOfferFormContainer>; | |||
| }; | |||
| SecondPartCreateOffer.propTypes = { | |||
| children: PropTypes.node, | |||
| handleOffer: PropTypes.func, | |||
| }; | |||
| export default SecondPartCreateOffer; | |||
| @@ -0,0 +1,8 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 700px; | |||
| padding-top: 20px; | |||
| `; | |||
| @@ -19,6 +19,7 @@ import { CheckBox } from "../../../../CheckBox/CheckBox"; | |||
| const FilterCheckboxDropdown = (props) => { | |||
| const [toSearch, setToSearch] = useState(""); | |||
| const [dataToShow, setDataToShow] = useState([]); | |||
| const [isOpened, setIsOpened] = useState(false); | |||
| const { data } = props; | |||
| useEffect(() => { | |||
| @@ -77,8 +78,9 @@ const FilterCheckboxDropdown = (props) => { | |||
| toggleIconClosed={<DropdownDown />} | |||
| toggleIconOpened={<DropdownUp />} | |||
| fullWidth | |||
| setIsOpened={setIsOpened} | |||
| toggleIconStyles={{ | |||
| backgroundColor: selectedTheme.primaryIconBackgroundColor, | |||
| backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor, | |||
| }} | |||
| headerOptions={ | |||
| <React.Fragment> | |||
| @@ -14,6 +14,7 @@ import RadioGroup from "../../../../Radio/Group/RadioGroup"; | |||
| const FilterRadioDropdown = (props) => { | |||
| const [toSearch, setToSearch] = useState(""); | |||
| const [dataToShow, setDataToShow] = useState([]); | |||
| const [isOpened, setIsOpened] = useState(false); | |||
| const { data } = props; | |||
| useEffect(() => { | |||
| @@ -51,8 +52,9 @@ const FilterRadioDropdown = (props) => { | |||
| toggleIconClosed={<DropdownDown />} | |||
| toggleIconOpened={<DropdownUp />} | |||
| fullWidth | |||
| setIsOpened={setIsOpened} | |||
| toggleIconStyles={{ | |||
| backgroundColor: selectedTheme.primaryIconBackgroundColor, | |||
| backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor, | |||
| }} | |||
| headerOptions={ | |||
| <React.Fragment> | |||
| @@ -14,31 +14,34 @@ import PropTypes from "prop-types"; | |||
| const DropdownList = (props) => { | |||
| const [listShown, setListShown] = useState(props.defaultOpen); | |||
| const handleShow = () => { | |||
| if (props.setIsOpened) { | |||
| props.setIsOpened(!listShown); | |||
| } | |||
| setListShown((prevState) => !prevState); | |||
| }; | |||
| return ( | |||
| <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | |||
| <DropdownHeader> | |||
| {props.dropdownIcon && ( | |||
| <DropdownIcon onClick={() => setListShown((prevState) => !prevState)}> | |||
| <DropdownIcon onClick={() => handleShow()}> | |||
| {props.dropdownIcon} | |||
| </DropdownIcon> | |||
| )} | |||
| <DropdownTitle | |||
| onClick={() => setListShown((prevState) => !prevState)} | |||
| textcolor={props.textcolor} | |||
| > | |||
| <DropdownTitle onClick={() => handleShow()} textcolor={props.textcolor}> | |||
| {props.title} | |||
| </DropdownTitle> | |||
| {listShown ? ( | |||
| <ToggleIconOpened | |||
| style={props.toggleIconStyles} | |||
| onClick={() => setListShown((prevState) => !prevState)} | |||
| onClick={() => handleShow()} | |||
| > | |||
| {props.toggleIconOpened} | |||
| </ToggleIconOpened> | |||
| ) : ( | |||
| <ToggleIconClosed | |||
| style={props.toggleIconStyles} | |||
| onClick={() => setListShown((prevState) => !prevState)} | |||
| onClick={() => handleShow()} | |||
| > | |||
| {props.toggleIconClosed} | |||
| </ToggleIconClosed> | |||
| @@ -65,6 +68,7 @@ DropdownList.propTypes = { | |||
| toggleIconStyles: PropTypes.any, | |||
| headerOptions: PropTypes.node, | |||
| textcolor: PropTypes.string, | |||
| setIsOpened: PropTypes.func, | |||
| }; | |||
| DropdownList.defaultProps = { | |||
| @@ -26,11 +26,17 @@ export const DropdownTitle = styled(Typography)` | |||
| export const ToggleIconOpened = styled(IconButton)` | |||
| cursor: pointer; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| & span { | |||
| ${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``} | |||
| } | |||
| `; | |||
| export const ToggleIconClosed = styled(IconButton)` | |||
| cursor: pointer; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| & span { | |||
| ${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``} | |||
| } | |||
| `; | |||
| export const DropdownIcon = styled(IconButton)` | |||
| @@ -12,11 +12,12 @@ import { | |||
| import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg"; | |||
| import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-grid-line.svg"; | |||
| import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg"; | |||
| import { MenuItem } from "@mui/material"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectFilters } from "../../../store/selectors/filtersSelectors"; | |||
| import Mockupdata from "../../Cards/FilterCard/Mockupdata"; | |||
| import Option from "../../Select/Option/Option"; | |||
| import { useHistory, useLocation, useRouteMatch } from "react-router-dom"; | |||
| const DownArrow = (props) => ( | |||
| <IconStyled {...props}> | |||
| @@ -31,11 +32,15 @@ const MockupdataForSelect = [ | |||
| }, | |||
| { | |||
| id: 1, | |||
| string: "Ceni rastuce", | |||
| string: "Najpopularnije", | |||
| }, | |||
| { | |||
| id: 2, | |||
| string: "Ceni opadajuce", | |||
| string: "Najnovije", | |||
| }, | |||
| { | |||
| id: 3, | |||
| string: "Najstarije", | |||
| }, | |||
| ]; | |||
| @@ -43,6 +48,9 @@ const Header = (props) => { | |||
| const [categoryString, setCategoryString] = useState(""); | |||
| const [filtersString, setFiltersString] = useState(""); | |||
| const { category, cities } = useSelector(selectFilters); | |||
| const history = useHistory(); | |||
| const location = useLocation(); | |||
| const routeMatch = useRouteMatch(); | |||
| useEffect(() => { | |||
| let categorystring = ""; | |||
| @@ -67,6 +75,15 @@ const Header = (props) => { | |||
| setCategoryString(categorystring); | |||
| setFiltersString(filtersstring); | |||
| }, [category, cities]); | |||
| const handleChangeSelect = (value) => { | |||
| let chosenOption = MockupdataForSelect.find(item => item.id === value); | |||
| console.log(location); | |||
| console.log(history); | |||
| console.log(chosenOption); | |||
| console.log(routeMatch) | |||
| } | |||
| return ( | |||
| <HeaderContainer> | |||
| <HeaderLocation>{categoryString + filtersString} </HeaderLocation> | |||
| @@ -93,15 +110,21 @@ const Header = (props) => { | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| </HeaderButtons> | |||
| <HeaderSelect defaultValue={0} IconComponent={DownArrow}> | |||
| <HeaderSelect | |||
| defaultValue={0} | |||
| IconComponent={DownArrow} | |||
| width="209px" | |||
| height="34px" | |||
| onChange={handleChangeSelect} | |||
| > | |||
| {MockupdataForSelect.map((item) => ( | |||
| <MenuItem | |||
| <Option | |||
| value={item.id} | |||
| key={item.id} | |||
| style={{ display: item.id === 0 ? "none" : "flex" }} | |||
| > | |||
| {item.string} | |||
| </MenuItem> | |||
| </Option> | |||
| ))} | |||
| </HeaderSelect> | |||
| </HeaderOptions> | |||
| @@ -1,7 +1,8 @@ | |||
| import { Box, MenuItem, Select } from "@mui/material"; | |||
| import { Box, MenuItem } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import Select from "../../Select/Select"; | |||
| export const HeaderContainer = styled(Box)` | |||
| margin-top: 20px; | |||
| @@ -34,9 +35,11 @@ export const HeaderSelect = styled(Select)` | |||
| height: 35px; | |||
| font-family: "Open Sans"; | |||
| margin-top: 3px; | |||
| & div span { | |||
| position: relative; | |||
| top: -4px; | |||
| font-weight: 400; | |||
| position: relative; | |||
| left: -5px; | |||
| & div { | |||
| padding-left: 8px; | |||
| } | |||
| ` | |||
| export const SelectItem = styled(MenuItem)` | |||
| @@ -6,7 +6,6 @@ import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg"; | |||
| // import {Select as SelectMUI} from "@mui/material"; | |||
| const Select = (props) => { | |||
| console.log(props); | |||
| return ( | |||
| <SelectStyled | |||
| defaultValue={props.defaultValue} | |||
| @@ -14,6 +13,7 @@ const Select = (props) => { | |||
| width={props.width} | |||
| height={props.height} | |||
| className={props.className} | |||
| onChange={props.onChange} | |||
| IconComponent={(iconProps) => ( | |||
| <SelectIcon {...iconProps}> | |||
| <Down /> | |||
| @@ -32,6 +32,7 @@ Select.propTypes = { | |||
| fullwidth: PropTypes.bool, | |||
| defaultValue: PropTypes.number, | |||
| className: PropTypes.string, | |||
| onChange: PropTypes.func, | |||
| }; | |||
| Select.defaultProps = { | |||
| fullwidth: true, | |||
| @@ -43,7 +43,7 @@ export const TextField = (props) => { | |||
| label={props.showAnimation ? props.placeholder : ""} | |||
| italicplaceholder={props.italicPlaceholder && isFieldEmpty} | |||
| italicplaceholder={(props.italicPlaceholder && isFieldEmpty) ? "true" : "false"} | |||
| ref={textInputRef} | |||
| focused={props.focused} | |||
| > | |||
| @@ -14,7 +14,7 @@ export const TextFieldStyled = styled(TextField)` | |||
| background-color: ${selectedTheme.primaryBackgroundColor}; | |||
| width: ${(props) => props.width}; | |||
| font-style: ${(props) => | |||
| props.italicplaceholder === true ? "italic" : "normal"}; | |||
| props.italicplaceholder === "true" ? "italic" : "normal"}; | |||
| padding-left: 0; | |||
| margin: 0; | |||
| padding: 0; | |||
| @@ -7,5 +7,5 @@ export const NOT_FOUND_PAGE = '/not-found'; | |||
| export const FORGOT_PASSWORD_MAIL_SENT = '/forgot-password/mail-sent' | |||
| export const REGISTER_PAGE = "/register" | |||
| export const REGISTER_SUCCESSFUL_PAGE = "/register/success"; | |||
| export const RESET_PASSWORD_PAGE = "/reset-password" | |||
| export const RESET_PASSWORD_PAGE = "/reset-password/:token" | |||
| export const CREATE_OFFER_PAGE = "/create-offer" | |||
| @@ -55,7 +55,7 @@ export default { | |||
| emailRequired: 'Email adresa je obavezna!', | |||
| noUsers: 'Ne postoji korisnik sa zadatom email adresom.', | |||
| passwordStrength: 'Your password is {{strength}}.', | |||
| passwordLength: 'Lozinka mora imati izmedju 8 i 50 karaktera!', | |||
| passwordLength: 'Lozinka mora imati najmanje 8 karaktera!', | |||
| signUpRecommendation: 'Registrujte se.', | |||
| email: 'Unesite email adresu kako biste se prijavili', | |||
| logInTitle: 'Uloguj se', | |||
| @@ -66,6 +66,7 @@ export default { | |||
| forgotYourPassword: 'Zaboravili ste lozinku?', | |||
| forgotPasswordEmail:'Email', | |||
| useDifferentEmail: 'Iskoristite drugačiju lozinku.', | |||
| wrongCredentials: 'Pogrešan mail ili lozinka!' | |||
| }, | |||
| password: { | |||
| weak: 'slaba', | |||
| @@ -80,11 +81,14 @@ export default { | |||
| descriptionSecond: 'Ovaj korak nije obavezan za razgledanje artikla ali za proces trampe je obavezan. Uvek možete popuniti ova polja u podešavanjima naloga kasnije', | |||
| descriptionThird: 'Ovaj korak nije obavezan za razgledanje artikla ali za proces trampe je obavezan. Uvek možete popuniti ova polja u podešavanjima naloga kasnije', | |||
| loginText: "Već posedujete nalog?", | |||
| emailFormat: 'Nevalidan format email adrese!', | |||
| emailTaken: "E-mail je zauzet!", | |||
| login: "Ulogujte se.", | |||
| acceptTerms: `Pri klikom na dugme "Registruj se", prihvatate naše`, | |||
| terms: "Uslove Korišćenja", | |||
| success: 'Registracija Uspešna', | |||
| welcome: 'Dobro došli na trampu, želimo vam uspešno trampovanje!' | |||
| PIBTaken: "PIB je zauzet!", | |||
| welcome: 'Dobro došli na trampu, želimo vam uspešno trampovanje!', | |||
| }, | |||
| forgotPassword: { | |||
| title: 'Povrati lozinku', | |||
| @@ -94,6 +98,7 @@ export default { | |||
| emailFormat: 'Nevalidan format email adrese!', | |||
| mailSent: "E-Mail poslat!", | |||
| mailSentDescription: "Poslat vam je email sa instrukcijama kako da resetujete lozinku", | |||
| mailNotFound: "Mejl nije povezan ni sa jednim nalogom!", | |||
| notRecievedMail: "Niste dobili email?", | |||
| checkSpam: "U slučaju da Vam ne stigne email, pogledajte <strong>Spam</strong> folder email provajdera", | |||
| forgotPassword: { | |||
| @@ -44,4 +44,11 @@ export const ForgotPasswordDescription = styled(Typography)` | |||
| export const FormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 216px; | |||
| `; | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: "Open Sans"; | |||
| position: relative; | |||
| top: -7px; | |||
| font-size: 14px; | |||
| ` | |||
| @@ -29,19 +29,18 @@ const MailSent = () => { | |||
| useEffect(() => { | |||
| setEmail(location.state.email); | |||
| }, []) | |||
| }, []); | |||
| const navigateLogin = () => { | |||
| history.replace(LOGIN_PAGE); | |||
| }; | |||
| const handleResend = () => { | |||
| dispatch(forgotPassword({email: mail})) | |||
| } | |||
| dispatch(forgotPassword({ email: mail })); | |||
| }; | |||
| return ( | |||
| <MailSentContainer> | |||
| <MailSentImage /> | |||
| <Title component="h1" variant="h5"> | |||
| @@ -53,12 +52,11 @@ const MailSent = () => { | |||
| </Description> | |||
| <FormContainer> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| onClick={navigateLogin} | |||
| @@ -67,27 +65,26 @@ const MailSent = () => { | |||
| </PrimaryButton> | |||
| <SendAgainTextContainer> | |||
| <StandardText> | |||
| {t("forgotPassword.notRecievedMail")} | |||
| </StandardText> | |||
| <Link to="#" component={NavLink} underline="hover" align="center" textsize="16px" onClick={handleResend}> | |||
| <StandardText>{t("forgotPassword.notRecievedMail")}</StandardText> | |||
| <Link | |||
| to="#" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="center" | |||
| textsize="16px" | |||
| onClick={handleResend} | |||
| > | |||
| {t("common.sendAgain")} | |||
| </Link> | |||
| </SendAgainTextContainer> | |||
| </FormContainer> | |||
| <Footer> | |||
| <FooterText> | |||
| <Trans i18nKey="forgotPassword.checkSpam" /> | |||
| <Trans i18nKey="forgotPassword.checkSpam" /> | |||
| </FooterText> | |||
| </Footer> | |||
| </MailSentContainer> | |||
| ); | |||
| }; | |||
| @@ -1,62 +1,116 @@ | |||
| import React from 'react'; | |||
| import { Formik, Form, Field } from 'formik'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import * as Yup from 'yup'; | |||
| 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/Buttons/Button'; | |||
| import Section from '../../components/Section/Section'; | |||
| const forgotPasswordValidationSchema = Yup.object().shape({ | |||
| email: Yup.string().required( | |||
| i18next.t('login.securityQuestion.answerRequired'), | |||
| ), | |||
| }); | |||
| import React, { useState } from "react"; | |||
| import { useFormik } from "formik"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import * as Yup from "yup"; | |||
| // import i18next from "i18next"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { | |||
| ForgotPasswordPageContainer, | |||
| ForgotPasswordDescription, | |||
| ForgotPasswordTitle, | |||
| FormContainer, | |||
| ErrorMessage, | |||
| } from "./ForgotPassword.styled"; | |||
| import { TextField } from "../../components/TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { FORGOT_PASSWORD_MAIL_SENT } from "../../constants/pages"; | |||
| import selectedTheme from "../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { forgotPassword } from "../../store/actions/user/userActions"; | |||
| const ForgotPasswordPage = () => { | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const [emailNotFoundStatus, setEmailNotFoundStatus] = useState(false); | |||
| const forgotPasswordValidationSchema = Yup.object().shape({ | |||
| email: Yup.string() | |||
| .required(t("forgotPassword.emailRequired")) | |||
| .email(t("forgotPassword.emailFormat")), | |||
| }); | |||
| const handleResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: FORGOT_PASSWORD_MAIL_SENT, | |||
| state: { email: formik.values.email }, | |||
| }); | |||
| }; | |||
| const handleResponseError = () => { | |||
| setEmailNotFoundStatus(true); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| console.log("Values",values) | |||
| dispatch( | |||
| forgotPassword({ | |||
| email: values.email, | |||
| handleResponseSuccess, | |||
| handleResponseError, | |||
| }) | |||
| ); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| email: "", | |||
| }, | |||
| validationSchema: forgotPasswordValidationSchema, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <Auth> | |||
| <AuthCard | |||
| title={t('forgotPassword.title')} | |||
| > | |||
| <Section> | |||
| <div className="c-reset-security"> | |||
| <div className="c-reset-security__form"> | |||
| <Formik | |||
| onSubmit={handleSubmit} | |||
| initialValues={{ email: '' }} | |||
| validationSchema={forgotPasswordValidationSchema} | |||
| > | |||
| <Form> | |||
| <Field | |||
| label={t('login.forgotPasswordEmail')} | |||
| name="email" | |||
| component={TextField} | |||
| /> | |||
| <Button | |||
| className="c-reset-security__button" | |||
| authButton | |||
| variant="primary" | |||
| type="submit" | |||
| > | |||
| {t('forgotPassword.label')} | |||
| </Button> | |||
| </Form> | |||
| </Formik> | |||
| </div> | |||
| </div> | |||
| </Section> | |||
| </AuthCard> | |||
| </Auth> | |||
| <ForgotPasswordPageContainer> | |||
| <Logo /> | |||
| <ForgotPasswordTitle component="h1" variant="h5"> | |||
| {t("forgotPassword.title")} | |||
| </ForgotPasswordTitle> | |||
| <ForgotPasswordDescription component="h1" variant="h6"> | |||
| {t("forgotPassword.description")} | |||
| </ForgotPasswordDescription> | |||
| <FormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <TextField | |||
| name="email" | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| error={ | |||
| (formik.touched.email && Boolean(formik.errors.email)) || | |||
| emailNotFoundStatus | |||
| } | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| onChange={formik.handleChange} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| {formik.errors.email && formik.touched.email && ( | |||
| <ErrorMessage>{formik.errors.email}</ErrorMessage> | |||
| )} | |||
| {emailNotFoundStatus && ( | |||
| <ErrorMessage>{t("forgotPassword.mailNotFound")}</ErrorMessage> | |||
| )} | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={formik.values.email?.length === 0} | |||
| > | |||
| {t("common.send")} | |||
| </PrimaryButton> | |||
| </FormContainer> | |||
| </ForgotPasswordPageContainer> | |||
| ); | |||
| }; | |||
| @@ -1,101 +0,0 @@ | |||
| import React, { useState } from "react"; | |||
| import { useFormik } from "formik"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import * as Yup from "yup"; | |||
| import i18next from "i18next"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { | |||
| ForgotPasswordPageContainer, | |||
| ForgotPasswordDescription, | |||
| ForgotPasswordTitle, | |||
| FormContainer, | |||
| } from "./ForgotPassword.styled"; | |||
| import { TextField } from "../../components/TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { FORGOT_PASSWORD_MAIL_SENT } from "../../constants/pages"; | |||
| import selectedTheme from "../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { forgotPassword } from "../../store/actions/user/userActions"; | |||
| const forgotPasswordValidationSchema = Yup.object().shape({ | |||
| email: Yup.string() | |||
| .required(i18next.t("forgotPassword.emailRequired")) | |||
| .email(i18next.t("forgotPassword.emailFormat")), | |||
| }); | |||
| const ForgotPasswordPage = () => { | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const [emailNotFoundStatus, setEmailNotFoundStatus] = useState(false); | |||
| const handleResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: FORGOT_PASSWORD_MAIL_SENT, | |||
| state: {email: formik.values.email} | |||
| }); | |||
| } | |||
| const handleResponseError = () => { | |||
| setEmailNotFoundStatus(true); | |||
| } | |||
| const handleSubmit = (values) => { | |||
| // validate email | |||
| dispatch(forgotPassword({email: values.email, handleResponseSuccess, handleResponseError})); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| email: "", | |||
| }, | |||
| validationSchema: forgotPasswordValidationSchema, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <ForgotPasswordPageContainer> | |||
| <Logo /> | |||
| <ForgotPasswordTitle component="h1" variant="h5"> | |||
| {t("forgotPassword.title")} | |||
| </ForgotPasswordTitle> | |||
| <ForgotPasswordDescription component="h1" variant="h6"> | |||
| {t("forgotPassword.description")} | |||
| </ForgotPasswordDescription> | |||
| <FormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <TextField | |||
| name="email" | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| error={(formik.touched.email && Boolean(formik.errors.email)) || emailNotFoundStatus} | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| onChange={formik.handleChange} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={formik.values.email?.length === 0} | |||
| > | |||
| {t("common.send")} | |||
| </PrimaryButton> | |||
| </FormContainer> | |||
| </ForgotPasswordPageContainer> | |||
| ); | |||
| }; | |||
| export default ForgotPasswordPage; | |||
| @@ -20,15 +20,20 @@ const HomePage = () => { | |||
| const history = useHistory(); | |||
| useEffect(() => { | |||
| let category = null, subcategory = null, cities = [], _des_date = false, _des_popular = false, page, size; | |||
| const queryString = history.location.search.substring(1); | |||
| const queryObject = qs.parse(queryString); | |||
| let category = null; | |||
| if (queryObject.category) { | |||
| category = Mockupdata[1].find( | |||
| (item) => item.string === queryObject.category.toString() | |||
| ).id; | |||
| } | |||
| let cities = []; | |||
| if (queryObject.subcategory) { | |||
| subcategory = Mockupdata[1].find( | |||
| (item) => item.string === queryObject.subcategory.toString() | |||
| ).id; | |||
| } | |||
| if (queryObject.city) { | |||
| if (Array.isArray(queryObject.city)) { | |||
| queryObject.city.forEach((item) => { | |||
| @@ -40,44 +45,25 @@ const HomePage = () => { | |||
| ); | |||
| } | |||
| } | |||
| let subcategory = null; | |||
| if (queryObject.subcategory) { | |||
| subcategory = Mockupdata[1].find( | |||
| (item) => item.string === queryObject.subcategory.toString() | |||
| ).id; | |||
| if (queryObject.sortBy) { | |||
| if (queryObject.sortBy === "dateAsc") _des_date = false; | |||
| if (queryObject.sortBy === "dateDesc") _des_date = true; | |||
| if (queryObject.sortBy === "popular") _des_popular = true; | |||
| } | |||
| if (queryObject.page) { | |||
| page = queryObject.page; | |||
| } | |||
| if (queryObject.size) { | |||
| size = queryObject.size; | |||
| } | |||
| dispatch(setFilters({ category, subcategory, cities })); | |||
| dispatch(setFilters({ category, subcategory, cities, _des_date, _des_popular, page, size })); | |||
| }, [history.location.search]); | |||
| // const handleCl = () => { | |||
| // dispatch(logoutUser()); | |||
| // }; | |||
| return ( | |||
| <HomePageContainer> | |||
| {/* <button onClick={handleCl}>Dugme</button> */} | |||
| <Navbar /> | |||
| <MainLayout leftCard={<FilterCard />} content={<MarketPlace />} /> | |||
| {/* <Box sx={{ mt: 4, mx: 4 }}> | |||
| <GridStyled container justifyContent="space-between"> | |||
| <GridStyled item xs={12} md={3}> | |||
| asdasdasd | |||
| </GridStyled> | |||
| <GridStyled item xs={12} md={6}> | |||
| <GridStyled xs={12} md={12}> | |||
| <HomeListCard></HomeListCard> | |||
| </GridStyled> | |||
| </GridStyled> */} | |||
| {/* <GridStyled item xs={12} md={9}> | |||
| <PagingSortingFiltering /> | |||
| </GridStyled> | |||
| <GridStyled item xs={12} md={9}> | |||
| {/* Move to higher components? */} | |||
| {/* <RandomDataProvider> | |||
| <PagingSortingFilteringServerSide /> | |||
| </RandomDataProvider> | |||
| </GridStyled> */} | |||
| {/* </GridStyled> | |||
| </Box> */} | |||
| </HomePageContainer> | |||
| ); | |||
| }; | |||
| @@ -51,10 +51,18 @@ export const RegisterAltText = styled(Typography)` | |||
| font-size: 14px; | |||
| padding-right: 6px; | |||
| line-height: 14px; | |||
| ` | |||
| `; | |||
| export const RegisterTextContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| margin-top: 36px; | |||
| justify-content: center; | |||
| ` | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: "Open Sans"; | |||
| position: relative; | |||
| top: -12px; | |||
| height: 20px; | |||
| font-size: 14px; | |||
| `; | |||
| @@ -1,135 +1,213 @@ | |||
| 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/Buttons/Button'; | |||
| import TextField from '../../components/InputFields/TextField'; | |||
| import Auth from '../../components/Auth/Auth'; | |||
| import AuthCard from '../../components/AuthCards/AuthCard'; | |||
| /* eslint-disable */ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useFormik } 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 { | |||
| clearLoginErrors, | |||
| fetchUser, | |||
| } from '../../store/actions/login/loginActions'; | |||
| } from "../../store/actions/login/loginActions"; | |||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg"; | |||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { LOGIN_USER_LOADING } from "../../store/actions/login/loginActionConstants"; | |||
| import { TextField } from "../../components/TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { IconButton } from "../../components/Buttons/IconButton/IconButton"; | |||
| import Link from "../../components/Link/Link"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| 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({ | |||
| username: Yup.string().required(i18next.t('login.usernameRequired')), | |||
| password: Yup.string().required(i18next.t('login.passwordRequired')), | |||
| }); | |||
| LoginPageContainer, | |||
| LoginTitle, | |||
| LoginDescription, | |||
| LoginFormContainer, | |||
| RegisterAltText, | |||
| RegisterTextContainer, | |||
| ErrorMessage, | |||
| } from "./Login.styled"; | |||
| import selectedTheme from "../../themes"; | |||
| const LoginPage = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const error = useSelector(selectLoginError); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
| // When user refreshes page | |||
| // useEffect(() => { | |||
| // function redirectClient() { | |||
| // if (!tokens.RefreshToken && !tokens.JwtToken) { | |||
| // return | |||
| // } | |||
| // } | |||
| // function redirectClient() { | |||
| // if (!tokens.RefreshToken && !tokens.JwtToken) { | |||
| // return; | |||
| // } | |||
| // } | |||
| // redirectClient(); | |||
| // redirectClient(); | |||
| // }, [history, tokens]); | |||
| const isLoading = useSelector( | |||
| selectIsLoadingByActionType(LOGIN_USER_LOADING), | |||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | |||
| ); | |||
| const handleApiResponseSuccess =()=>{ | |||
| useEffect(() => { | |||
| dispatch(clearLoginErrors()); | |||
| }, []); | |||
| const handleApiResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| } | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| // destructure value as username. | |||
| const { username: Username } = values; | |||
| const { password: Password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| Username, | |||
| Password, | |||
| handleApiResponseSuccess | |||
| }, | |||
| ), | |||
| ); | |||
| const { email, password: password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| email: "", | |||
| password: "", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| email: Yup.string().required(t("login.mailRequired")), | |||
| password: Yup.string() | |||
| .required(t("login.passwordRequired")) | |||
| .min(8, t("login.passwordLength")), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| useEffect(() => { | |||
| if (error) { | |||
| if (formik.errors.email || formik.errors.password) { | |||
| dispatch(clearLoginErrors()); | |||
| } | |||
| } | |||
| }, [formik.errors.email, formik.errors.password]) | |||
| return ( | |||
| <Auth> | |||
| <AuthCard | |||
| title="Log In" | |||
| isLoading={isLoading} | |||
| > | |||
| <div className="c-login c-login--user"> | |||
| <div className="c-login__form"> | |||
| <Formik | |||
| initialValues={{ | |||
| username: '', | |||
| password: '', | |||
| }} | |||
| onSubmit={handleSubmit} | |||
| validationSchema={LoginValidationSchema} | |||
| validateOnBlur | |||
| enableReinitialize | |||
| > | |||
| {({ values }) => ( | |||
| <Form> | |||
| <Field | |||
| label={t('common.labelUsername')} | |||
| value={values.username.value} | |||
| component={TextField} | |||
| name="username" | |||
| /> | |||
| <Field | |||
| label={ | |||
| <div className="c-login--password__label"> | |||
| {t('common.labelPassword')} | |||
| </div> | |||
| } | |||
| link={ | |||
| <NavLink | |||
| to={FORGOT_PASSWORD_PAGE} | |||
| > | |||
| {t('login.forgotYourPassword')} | |||
| </NavLink> | |||
| } | |||
| name="password" | |||
| component={PasswordField} | |||
| errorMessage={error} | |||
| autoFocus | |||
| /> | |||
| <Button | |||
| className="c-login__button" | |||
| authButton | |||
| variant="primary" | |||
| type="submit" | |||
| > | |||
| {t('common.continue')} | |||
| </Button> | |||
| </Form> | |||
| )} | |||
| </Formik> | |||
| </div> | |||
| </div> | |||
| </AuthCard> | |||
| </Auth> | |||
| <LoginPageContainer> | |||
| <Logo /> | |||
| <LoginTitle component="h1" variant="h5"> | |||
| {t("login.logInTitle")} | |||
| </LoginTitle> | |||
| <LoginDescription component="h1" variant="h6"> | |||
| {t("login.welcomeText")} | |||
| </LoginDescription> | |||
| <LoginFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| <Backdrop position="absolute" isLoading={isLoading} /> | |||
| <TextField | |||
| name="email" | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| onChange={formik.handleChange} | |||
| error={ | |||
| (formik.touched.email && formik.errors.email) || error.length > 0 | |||
| } | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="password" | |||
| placeholder={t("common.labelPassword")} | |||
| margin="normal" | |||
| type={showPassword ? "text" : "password"} | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={ | |||
| (formik.touched.password && formik.errors.password) || | |||
| error.length > 0 | |||
| } | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconButton | |||
| onClick={handleClickShowPassword} | |||
| onMouseDown={handleMouseDownPassword} | |||
| > | |||
| {showPassword ? <VisibilityOn /> : <VisibilityOff />} | |||
| </IconButton> | |||
| ), | |||
| }} | |||
| /> | |||
| {formik.errors.password && formik.touched.password && ( | |||
| <ErrorMessage>{formik.errors.password}</ErrorMessage> | |||
| )} | |||
| {error.length > 0 && !formik.errors.password && <ErrorMessage>{error}</ErrorMessage>} | |||
| <Link | |||
| to={FORGOT_PASSWORD_PAGE} | |||
| textsize="12px" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="right" | |||
| style={{ | |||
| marginTop: error.length > 0 ? "0" : "18px", | |||
| marginBottom: "18px", | |||
| }} | |||
| > | |||
| {t("login.forgotYourPassword")} | |||
| </Link> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| formik.values.email.length === 0 || | |||
| formik.values.password.length === 0 | |||
| } | |||
| > | |||
| {t("login.logIn")} | |||
| </PrimaryButton> | |||
| <RegisterTextContainer> | |||
| <RegisterAltText> | |||
| {t("login.dontHaveAccount").padEnd(2, " ")} | |||
| </RegisterAltText> | |||
| <Link | |||
| to="/register" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="center" | |||
| > | |||
| {t("login.signUp")} | |||
| </Link> | |||
| </RegisterTextContainer> | |||
| </LoginFormContainer> | |||
| </LoginPageContainer> | |||
| ); | |||
| }; | |||
| @@ -1,201 +0,0 @@ | |||
| /* eslint-disable */ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useFormik } 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 { | |||
| clearLoginErrors, | |||
| fetchUser, | |||
| } from "../../store/actions/login/loginActions"; | |||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||
| import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg"; | |||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { LOGIN_USER_LOADING } from "../../store/actions/login/loginActionConstants"; | |||
| import { TextField } from "../../components/TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { IconButton } from "../../components/Buttons/IconButton/IconButton"; | |||
| import Link from "../../components/Link/Link"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { | |||
| LoginPageContainer, | |||
| LoginTitle, | |||
| LoginDescription, | |||
| LoginFormContainer, | |||
| RegisterAltText, | |||
| RegisterTextContainer, | |||
| } from "./Login.styled"; | |||
| import selectedTheme from "../../themes"; | |||
| const LoginPage = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const error = useSelector(selectLoginError); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
| // When user refreshes page | |||
| // useEffect(() => { | |||
| // function redirectClient() { | |||
| // if (!tokens.RefreshToken && !tokens.JwtToken) { | |||
| // return; | |||
| // } | |||
| // } | |||
| // redirectClient(); | |||
| // }, [history, tokens]); | |||
| const isLoading = useSelector( | |||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | |||
| ); | |||
| useEffect(() => { | |||
| dispatch(clearLoginErrors()); | |||
| }, []); | |||
| const handleApiResponseSuccess = (status) => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| const { username: email, password: password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| username: "", | |||
| password: "", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| username: Yup.string().required(t("login.usernameRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")).min(8), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <LoginPageContainer> | |||
| <Logo /> | |||
| <LoginTitle component="h1" variant="h5"> | |||
| {t("login.logInTitle")} | |||
| </LoginTitle> | |||
| <LoginDescription component="h1" variant="h6"> | |||
| {t("login.welcomeText")} | |||
| </LoginDescription> | |||
| <LoginFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| <Backdrop position="absolute" isLoading={isLoading} /> | |||
| <TextField | |||
| name="username" | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.username} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.password && formik.errors.password) || error.length > 0} | |||
| helperText={formik.touched.username && formik.errors.username} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="password" | |||
| placeholder={t("common.labelPassword")} | |||
| margin="normal" | |||
| type={showPassword ? "text" : "password"} | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.password && formik.errors.password) || error.length > 0} | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth={true} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconButton | |||
| onClick={handleClickShowPassword} | |||
| onMouseDown={handleMouseDownPassword} | |||
| > | |||
| {showPassword ? <VisibilityOn /> : <VisibilityOff />} | |||
| </IconButton> | |||
| ), | |||
| }} | |||
| /> | |||
| <Link | |||
| to={FORGOT_PASSWORD_PAGE} | |||
| textsize="12px" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="right" | |||
| style={{ marginTop: "18px", marginBottom: "18px" }} | |||
| > | |||
| {t("login.forgotYourPassword")} | |||
| </Link> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| formik.values.username.length === 0 || | |||
| formik.values.password.length === 0 | |||
| } | |||
| > | |||
| {t("login.logIn")} | |||
| </PrimaryButton> | |||
| <RegisterTextContainer> | |||
| <RegisterAltText> | |||
| {t("login.dontHaveAccount").padEnd(2, " ")} | |||
| </RegisterAltText> | |||
| <Link | |||
| to="/register" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="center" | |||
| > | |||
| {t("login.signUp")} | |||
| </Link> | |||
| </RegisterTextContainer> | |||
| </LoginFormContainer> | |||
| </LoginPageContainer> | |||
| ); | |||
| }; | |||
| LoginPage.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default LoginPage; | |||
| @@ -32,8 +32,12 @@ const FirstPartOfRegistration = (props) => { | |||
| password: "", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| mail: Yup.string().email().required(t("login.usernameRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")).min(8), | |||
| mail: Yup.string() | |||
| .email(t("forgotPassword.emailFormat")) | |||
| .required(t("login.usernameRequired")), | |||
| password: Yup.string() | |||
| .required(t("login.passwordRequired")) | |||
| .min(8, t("login.passwordLength")), | |||
| }), | |||
| onSubmit: props.handleSubmit, | |||
| validateOnBlur: true, | |||
| @@ -54,15 +58,22 @@ const FirstPartOfRegistration = (props) => { | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.mail} | |||
| onChange={(value) => | |||
| formik.setFieldValue("mail", value.target.value) | |||
| onChange={(value) => formik.setFieldValue("mail", value.target.value)} | |||
| error={ | |||
| (formik.touched.mail && Boolean(formik.errors.mail)) || | |||
| emailTakenStatus | |||
| } | |||
| error={(formik.touched.mail && Boolean(formik.errors.mail)) || emailTakenStatus} | |||
| helperText={formik.touched.mail && formik.errors.mail} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| {formik.errors.mail && formik.touched.mail ? ( | |||
| <ErrorMessage>{formik.errors.mail}</ErrorMessage> | |||
| ) : ( | |||
| <></> | |||
| )} | |||
| <TextField | |||
| name="password" | |||
| placeholder={t("common.labelPassword")} | |||
| @@ -72,14 +83,8 @@ const FirstPartOfRegistration = (props) => { | |||
| onChange={(value) => | |||
| formik.setFieldValue("password", value.target.value) | |||
| } | |||
| error={ | |||
| formik.touched.password && | |||
| Boolean(formik.errors.password) | |||
| } | |||
| helperText={ | |||
| formik.touched.password && | |||
| formik.errors.password | |||
| } | |||
| error={formik.touched.password && Boolean(formik.errors.password)} | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| @@ -89,14 +94,18 @@ const FirstPartOfRegistration = (props) => { | |||
| ), | |||
| }} | |||
| /> | |||
| {props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)} | |||
| {formik.errors.password && formik.touched.password ? ( | |||
| <ErrorMessage>{formik.errors.password}</ErrorMessage> | |||
| ) : ( | |||
| <></> | |||
| )} | |||
| {props.error && <ErrorMessage>{props.errorMessage}</ErrorMessage>} | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| @@ -19,6 +19,9 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-top: 31px; | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| @media (max-height: 800px) { | |||
| margin-top: 14px; | |||
| } | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| @@ -27,11 +27,11 @@ const Register = () => { | |||
| const history = useHistory(); | |||
| const dispatch = useDispatch(); | |||
| const [currentStep, setCurrentStep] = useState(1); | |||
| const [informations, setInformations] = useState({}); | |||
| const [mailError, setMailError] = useState(""); | |||
| const [mailErrorMessage, setMailErrorMessage] = useState(""); | |||
| const [PIBErrorMessage, setPIBErrorMessage] = useState(""); | |||
| const [PIBError, setPIBError] = useState(""); | |||
| const [informations, setInformations] = useState({}); // Values of fields typed in all steps | |||
| const [mailError, setMailError] = useState(""); // Wrong mail typed | |||
| const [mailErrorMessage, setMailErrorMessage] = useState(""); // Error message caused by typing wrong mail | |||
| const [PIBError, setPIBError] = useState(""); // Wrong PIB typed | |||
| const [PIBErrorMessage, setPIBErrorMessage] = useState(""); // Error message caused by typing wrong PIB | |||
| const handleResponseSuccess = () => { | |||
| history.push(REGISTER_SUCCESSFUL_PAGE); | |||
| @@ -43,23 +43,26 @@ const Register = () => { | |||
| setInformations({}); | |||
| setCurrentStep(1); | |||
| setMailError(mail); | |||
| if (error.error.response.data.toString() === 'User with email already exists') { | |||
| setMailErrorMessage("Vec postoji korisnik sa zadatim mail-om!"); | |||
| if ( | |||
| error.error.response.data.toString() === | |||
| "User with email already exists" | |||
| ) { | |||
| setMailErrorMessage(t("register.emailTaken")); | |||
| } else { | |||
| setMailErrorMessage("Nevalidan format mail-a!") | |||
| setMailErrorMessage(t("register.emailFormat")); | |||
| } | |||
| } else { | |||
| const { mail, password, PIB } = informations; | |||
| setInformations({ mail, password }); | |||
| setCurrentStep(2); | |||
| setPIBError(PIB.toString()); | |||
| setPIBErrorMessage("PIB broj je zauzet!"); | |||
| setPIBErrorMessage(t("register.PIBTaken")); | |||
| } | |||
| }; | |||
| const registerUser = (values) => { | |||
| dispatch( | |||
| fetchRegisterUser({values, handleResponseSuccess, handleResponseError}) | |||
| fetchRegisterUser({ values, handleResponseSuccess, handleResponseError }) | |||
| ); | |||
| }; | |||
| @@ -72,17 +75,19 @@ const Register = () => { | |||
| setInformations({ ...informations, ...values }); | |||
| }; | |||
| const goToFirst = () => { | |||
| setInformations({}); | |||
| setCurrentStep(1); | |||
| } | |||
| const goToSecond = () => { | |||
| const {mail, password} = informations; | |||
| setInformations({mail, password}); | |||
| setCurrentStep(2); | |||
| } | |||
| const goStepBack = (stepNumber) => { | |||
| setCurrentStep(stepNumber); | |||
| if (stepNumber === 1) { | |||
| setInformations({}); | |||
| } | |||
| if (stepNumber === 2) { | |||
| const { mail, password } = informations; | |||
| setInformations({ mail, password }); | |||
| } | |||
| }; | |||
| return ( | |||
| <RegisterPageContainer currentStep={currentStep}> | |||
| <RegisterPageContainer currentstep={currentStep}> | |||
| <Logo /> | |||
| <RegisterTitle component="h1" variant="h5"> | |||
| @@ -94,7 +99,11 @@ const Register = () => { | |||
| </RegisterDescription> | |||
| <ProgressContainer> | |||
| <StepProgress functions={[goToFirst, goToSecond]} current={currentStep} numberOfSteps={3} /> | |||
| <StepProgress | |||
| functions={[() => goStepBack(1), () => goStepBack(2)]} | |||
| current={currentStep} | |||
| numberOfSteps={3} | |||
| /> | |||
| </ProgressContainer> | |||
| {currentStep === 1 && ( | |||
| @@ -18,8 +18,8 @@ export const RegisterPageContainer = styled(Container)` | |||
| margin-top: 30px; | |||
| flex: none; | |||
| height: 95vh; | |||
| ${props => props.currentStep === 3 && `height: 105vh`}; | |||
| ${props => props.currentStep === 2 && `height: 100vh`}; | |||
| ${props => props.currentstep === 3 && `height: 105vh`}; | |||
| ${props => props.currentstep === 2 && `height: 100vh`}; | |||
| } | |||
| `; | |||
| export const RegisterTitle = styled(Typography)` | |||
| @@ -20,7 +20,7 @@ const SecondPartOfRegistration = (props) => { | |||
| if (props.error.length > 0) { | |||
| setPIBTakenStatus(true); | |||
| } | |||
| }, [props.error]) | |||
| }, [props.error]); | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| @@ -29,7 +29,10 @@ const SecondPartOfRegistration = (props) => { | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| nameOfFirm: Yup.string().required(t("login.usernameRequired")), | |||
| PIB: Yup.number().required(t("login.passwordRequired")).min(100000000).max(999999999), | |||
| PIB: Yup.number() | |||
| .required(t("login.passwordRequired")) | |||
| .min(100000000, "PIB mora imati 9 karaktera!") | |||
| .max(999999999, "PIB mora imati 9 karaktera!"), | |||
| }), | |||
| onSubmit: props.handleSubmit, | |||
| validateOnBlur: true, | |||
| @@ -60,18 +63,22 @@ const SecondPartOfRegistration = (props) => { | |||
| type="number" | |||
| value={formik.values.PIB} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.PIB && Boolean(formik.errors.PIB)) || PIBTakenStatus} | |||
| error={ | |||
| (formik.touched.PIB && Boolean(formik.errors.PIB)) || PIBTakenStatus | |||
| } | |||
| helperText={formik.touched.PIB && formik.errors.PIB} | |||
| fullWidth={true} | |||
| fullWidth | |||
| /> | |||
| {props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)} | |||
| {formik.errors.PIB && formik.touched.PIB && ( | |||
| <ErrorMessage>{formik.errors.PIB}</ErrorMessage> | |||
| )} | |||
| {props.error && <ErrorMessage>{props.errorMessage}</ErrorMessage>} | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| @@ -20,6 +20,9 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-top: 31px; | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| @media (max-height: 800px) { | |||
| margin-top: 14px; | |||
| } | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState } from "react"; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| FormContainer, | |||
| @@ -13,16 +13,6 @@ import selectedTheme from "../../../../themes"; | |||
| const ThirdPartOfRegistration = (props) => { | |||
| const { t } = useTranslation(); | |||
| const [phoneNumberTakenStatus, setPhoneNumberTakenStatus] = useState(false); | |||
| const handleForm = (values) => { | |||
| // validate email | |||
| if (true) { // eslint-disable-line | |||
| props.handleSubmit(values) | |||
| } else { | |||
| setPhoneNumberTakenStatus(true); | |||
| } | |||
| } | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| @@ -37,7 +27,7 @@ const ThirdPartOfRegistration = (props) => { | |||
| /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/gm | |||
| ), | |||
| }), | |||
| onSubmit: handleForm, | |||
| onSubmit: props.handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| @@ -55,7 +45,9 @@ const ThirdPartOfRegistration = (props) => { | |||
| type="number" | |||
| value={formik.values.phoneNumber} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)) || phoneNumberTakenStatus} | |||
| error={ | |||
| (formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)) | |||
| } | |||
| helperText={formik.touched.phoneNumber && formik.errors.phoneNumber} | |||
| autoFocus | |||
| fullWidth | |||
| @@ -70,7 +62,7 @@ const ThirdPartOfRegistration = (props) => { | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.location && Boolean(formik.errors.location)} | |||
| helperText={formik.touched.location && formik.errors.location} | |||
| fullWidth={true} | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| @@ -82,14 +74,14 @@ const ThirdPartOfRegistration = (props) => { | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.website && Boolean(formik.errors.website)} | |||
| helperText={formik.touched.website && formik.errors.website} | |||
| fullWidth={true} | |||
| fullWidth | |||
| /> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| @@ -20,6 +20,6 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| @media (max-height: 800px) { | |||
| margin-top: 21px; | |||
| margin-top: 14px; | |||
| } | |||
| `; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState } from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import { useFormik } from "formik"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import * as Yup from "yup"; | |||
| @@ -13,16 +13,16 @@ import { | |||
| } from "./ResetPasswordPage.styled"; | |||
| import { TextField } from "../../components/TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { LOGIN_PAGE } from "../../constants/pages"; | |||
| import { useHistory, useRouteMatch } from "react-router-dom"; | |||
| import { LOGIN_PAGE } from "../../constants/pages"; | |||
| import selectedTheme from "../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { resetPassword } from "../../store/actions/user/userActions"; | |||
| import { resetPassword } from "../../store/actions/user/userActions"; | |||
| import { Trans } from "react-i18next"; | |||
| import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg"; | |||
| import { IconButton } from "../../components/Buttons/IconButton/IconButton"; | |||
| import jwt from "jsonwebtoken"; | |||
| const ResetPasswordPage = () => { | |||
| const history = useHistory(); | |||
| @@ -30,6 +30,24 @@ const ResetPasswordPage = () => { | |||
| const dispatch = useDispatch(); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const [showPasswordConfirm, setShowPasswordConfirm] = useState(false); | |||
| const [error, setError] = useState(false); | |||
| const [token, setToken] = useState(""); | |||
| const routeMatch = useRouteMatch(); | |||
| useEffect(() => { | |||
| const tokenFromParams = routeMatch.params.token | |||
| setToken(tokenFromParams); | |||
| const data = jwt.decode(tokenFromParams); | |||
| if (!data || new Date() > new Date(data?.exp * 1000)) { | |||
| setError(true); | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| if (error) { | |||
| history.replace("/"); | |||
| } | |||
| }, [error]); | |||
| const handleClickShowPassword = () => { | |||
| setShowPassword((prevState) => !prevState); | |||
| @@ -37,21 +55,18 @@ const ResetPasswordPage = () => { | |||
| const handleClickShowPasswordConfirm = () => { | |||
| setShowPasswordConfirm((prevState) => !prevState); | |||
| }; | |||
| const handleResponseSuccess = () => { | |||
| history.push(LOGIN_PAGE); | |||
| } | |||
| const handleResponseError = () => { | |||
| console.log("error"); | |||
| } | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| // validate email | |||
| dispatch( | |||
| resetPassword({ | |||
| token: token, | |||
| password: values.password, | |||
| password2: values.passwordConfirm, | |||
| handleResponseSuccess, | |||
| handleResponseError | |||
| }) | |||
| ); | |||
| }; | |||
| @@ -110,8 +125,13 @@ const ResetPasswordPage = () => { | |||
| type={showPasswordConfirm ? "text" : "password"} | |||
| value={formik.values.passwordConfirm} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.passwordConfirm && formik.errors.passwordConfirm?.length > 0} | |||
| helperText={formik.touched.passwordConfirm && formik.errors.passwordConfirm} | |||
| error={ | |||
| formik.touched.passwordConfirm && | |||
| formik.errors.passwordConfirm?.length > 0 | |||
| } | |||
| helperText={ | |||
| formik.touched.passwordConfirm && formik.errors.passwordConfirm | |||
| } | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| @@ -126,7 +146,7 @@ const ResetPasswordPage = () => { | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| @@ -2,7 +2,7 @@ export default { | |||
| accounts: { | |||
| get: 'accounts/{accountUid}', | |||
| forgotPassword: 'forgot-password', | |||
| resetPassword: 'resetPassword', | |||
| resetPassword: 'reset-password', | |||
| getCurrentUserPermissions: | |||
| 'accounts/{currentAccountUid}/users/{currentUserUid}/permissions', | |||
| getAddresses: 'accounts/{accountUid}/addresses', | |||
| @@ -20,11 +20,13 @@ export default { | |||
| authentications: { | |||
| getUsernames: 'authenticate/usernames', | |||
| login: 'auth/token', | |||
| logout: 'auth/logout', | |||
| getUserSecurityQuestion: 'users/username/securityquestion', | |||
| confirmSecurityQuestion: 'authenticate/confirm', | |||
| confirmForgotPassword: 'users/passwords/reset_token', | |||
| resetPassword: 'users/passwords', | |||
| refreshToken: '/authenticate/refresh', | |||
| resetPassword: 'reset-password', | |||
| forgotPassword: 'forgot-password', | |||
| refreshToken: '/auth/refresh', | |||
| generateToken: '/authenticate/generate', | |||
| authenticate: | |||
| '/authenticate?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| @@ -156,6 +158,7 @@ export default { | |||
| setFingerprint: '/affiliate/fingerprint', | |||
| }, | |||
| offers: { | |||
| getOffers: 'offers' | |||
| getOffers: 'offers', | |||
| addOffer: 'offers', | |||
| } | |||
| }; | |||
| @@ -2,9 +2,14 @@ import { postRequest, getRequest } from "."; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const forgotPasswordRequest = (payload) => { | |||
| const encodedString = encodeURIComponent(payload); | |||
| return getRequest(`${apiEndpoints.accounts.forgotPassword}` + '/' + `${encodedString}`); | |||
| } | |||
| const encodedMail = encodeURIComponent(payload); | |||
| return getRequest( | |||
| `${apiEndpoints.authentications.forgotPassword}/${encodedMail}` | |||
| ); | |||
| }; | |||
| export const resetPasswordRequest = (payload) => | |||
| postRequest(apiEndpoints.accounts.resetPassword, payload); | |||
| postRequest( | |||
| `${apiEndpoints.authentications.resetPassword}/${payload.token}`, | |||
| { password: payload.password, password2: payload.password2 } | |||
| ); | |||
| @@ -1,14 +1,14 @@ | |||
| import axios from 'axios'; | |||
| import queryString from 'qs'; | |||
| import axios from "axios"; | |||
| import queryString from "qs"; | |||
| const request = axios.create({ | |||
| baseURL: "http://192.168.88.175:3005/", | |||
| baseURL: "http://192.168.88.150:3001/", | |||
| headers: { | |||
| 'Content-Type': 'application/json', | |||
| "Content-Type": "application/json", | |||
| }, | |||
| // withCredentials: true, | |||
| paramsSerializer: (params) => | |||
| queryString.stringify(params, { arrayFormat: 'comma' }), | |||
| queryString.stringify(params, { arrayFormat: "comma" }), | |||
| }); | |||
| export const getRequest = (url, params = null, options = null) => | |||
| @@ -27,7 +27,7 @@ export const deleteRequest = (url, params = null, options = null) => | |||
| request.delete(url, { params, ...options }); | |||
| export const downloadRequest = (url, params = null, options = null) => | |||
| request.get(url, { params, ...options, responseType: 'blob' }); | |||
| request.get(url, { params, ...options, responseType: "blob" }); | |||
| export const replaceInUrl = (url, pathVariables = {}) => { | |||
| const keys = Object.keys(pathVariables); | |||
| @@ -37,12 +37,11 @@ export const replaceInUrl = (url, pathVariables = {}) => { | |||
| return keys.reduce( | |||
| (acc, key) => acc.replace(`{${key}}`, pathVariables[`${key}`]), | |||
| url, | |||
| url | |||
| ); | |||
| }; | |||
| export const addHeaderToken = (token) => { | |||
| console.log(`Bearer ${token.toString()}`) | |||
| request.defaults.headers.Authorization = `Bearer ${token}`; | |||
| }; | |||
| @@ -54,11 +53,64 @@ export const removeHeaderToken = () => { | |||
| 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) => 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; | |||
| @@ -1,22 +1,10 @@ | |||
| import { getRequest, postRequest } from "./index"; | |||
| import { postRequest } from "./index"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const getUsernames = (emailorusername) => | |||
| getRequest(apiEndpoints.authentications.getUsernames, { | |||
| emailorusername, | |||
| }); | |||
| export const attemptLogin = (payload) => | |||
| postRequest(apiEndpoints.authentications.login, payload); | |||
| export const updateSecurityAnswer = (payload) => | |||
| postRequest(apiEndpoints.authentications.confirmSecurityQuestion, payload); | |||
| export const refreshTokenRequest = (payload) => | |||
| postRequest(apiEndpoints.authentications.refreshToken, payload); | |||
| export const logoutUserRequest = (payload) => | |||
| postRequest(apiEndpoints.users.logout, payload); | |||
| postRequest(apiEndpoints.authentications.logout, payload); | |||
| export const generateTokenRequest = (payload) => | |||
| postRequest(apiEndpoints.authentications.generateToken, payload); | |||
| @@ -1,6 +1,9 @@ | |||
| import { getRequest } from "." | |||
| import { getRequest, postRequest } from "." | |||
| import apiEndpoints from "./apiEndpoints" | |||
| export const attemptFetchOffers = () => { | |||
| return getRequest(apiEndpoints.offers.getOffers) | |||
| } | |||
| export const attemptAddOffer = (payload) => { | |||
| return postRequest(apiEndpoints.offers.addOffer, payload) | |||
| } | |||
| @@ -50,8 +50,9 @@ export const logoutUser = () => ({ | |||
| type: LOGOUT_USER, | |||
| }); | |||
| export const refreshUserToken = () => ({ | |||
| export const refreshUserToken = (payload) => ({ | |||
| type: REFRESH_TOKEN, | |||
| payload | |||
| }); | |||
| export const generateToken = (payload) => ({ | |||
| @@ -8,4 +8,5 @@ export const OFFERS_ERROR = createErrorType(OFFERS_SCOPE); | |||
| export const OFFERS_CLEAR = createClearType(OFFERS_SCOPE); | |||
| export const OFFERS_SET = "OFFERS_SET"; | |||
| export const OFFERS_ADD = "OFFERS_ADD"; | |||
| export const OFFERS_ADD = "OFFERS_ADD"; | |||
| export const OFFER_ADD = "OFFER_ADD"; | |||
| @@ -1,4 +1,4 @@ | |||
| import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_SET, OFFERS_SUCCESS } from "./offersActionConstants"; | |||
| import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_SET, OFFERS_SUCCESS, OFFER_ADD } from "./offersActionConstants"; | |||
| export const fetchOffers = (payload) => ({ | |||
| type: OFFERS_FETCH, | |||
| @@ -22,4 +22,8 @@ export const setOffers = (payload) => ({ | |||
| export const addOffers = (payload) => ({ | |||
| type: OFFERS_ADD, | |||
| payload | |||
| }) | |||
| export const addOffer = (payload) => ({ | |||
| type: OFFER_ADD, | |||
| payload | |||
| }) | |||
| @@ -2,4 +2,6 @@ | |||
| export const SET_USER = 'SET_USER'; | |||
| export const SET_USER_ERROR = 'SET_USER_ERROR'; | |||
| export const FORGOT_PASSWORD = "FORGOT_PASSWORD"; | |||
| export const RESET_PASSWORD = "RESET_PASSWORD"; | |||
| export const RESET_PASSWORD = "RESET_PASSWORD"; | |||
| export const SET_USER_ACCESS_TOKEN = "SET_USER_ACCESS_TOKEN"; | |||
| export const SET_USER_REFRESH_TOKEN = "SET_USER_REFRESH_TOKEN"; | |||
| @@ -2,7 +2,9 @@ import { | |||
| FORGOT_PASSWORD, | |||
| RESET_PASSWORD, | |||
| SET_USER, | |||
| SET_USER_ACCESS_TOKEN, | |||
| SET_USER_ERROR, | |||
| SET_USER_REFRESH_TOKEN, | |||
| } from './userActionConstants'; | |||
| export const setUser = (payload) => ({ | |||
| @@ -21,4 +23,12 @@ export const forgotPassword = (payload) => ({ | |||
| export const resetPassword = (payload) => ({ | |||
| type: RESET_PASSWORD, | |||
| payload | |||
| }) | |||
| export const setUserAccessToken = (payload) => ({ | |||
| type: SET_USER_ACCESS_TOKEN, | |||
| payload | |||
| }) | |||
| export const setUserRefreshToken = (payload) => ({ | |||
| type: SET_USER_REFRESH_TOKEN, | |||
| payload | |||
| }) | |||
| @@ -6,6 +6,7 @@ import loadingMiddleware from './middleware/loadingMiddleware'; | |||
| import requestStatusMiddleware from './middleware/requestStatusMiddleware'; | |||
| import internalServerErrorMiddleware from './middleware/internalServerErrorMiddleware'; | |||
| import persistStore from 'redux-persist/es/persistStore'; | |||
| import accessTokensMiddleware from './middleware/accessTokensMiddleware'; | |||
| const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; | |||
| @@ -18,6 +19,7 @@ export const store = createStore( | |||
| loadingMiddleware, | |||
| requestStatusMiddleware, | |||
| internalServerErrorMiddleware, | |||
| accessTokensMiddleware | |||
| ), | |||
| ), | |||
| ); | |||
| @@ -0,0 +1,50 @@ | |||
| import axios from "axios"; | |||
| import jwt from "jsonwebtoken"; | |||
| import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { | |||
| // addHeaderToken, | |||
| attachBeforeRequestListener, | |||
| } from "../../request/index"; | |||
| import { | |||
| authScopeStringGetHelper, | |||
| // authScopeSetHelper, | |||
| } from "../../util/helpers/authScopeHelpers"; | |||
| import { logoutUser, refreshUserToken } from "../actions/login/loginActions"; | |||
| // import { setUserAccessToken } from "../actions/user/userActions"; | |||
| //Change URL with .env | |||
| const baseURL = "http://192.168.88.150:3001/"; | |||
| //Interceptor unique name | |||
| export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR"; | |||
| export default ({ dispatch }) => | |||
| (next) => | |||
| (action) => { | |||
| attachBeforeRequestListener(async (response) => { | |||
| const jwtToken = authScopeStringGetHelper(JWT_TOKEN); | |||
| const refresh = authScopeStringGetHelper(JWT_REFRESH_TOKEN); | |||
| if (!jwtToken || !refresh) return Promise.resolve(response); | |||
| if (!response.headers?.Authorization) { | |||
| response.headers.Authorization = `Bearer ${jwtToken}`; | |||
| } | |||
| const jwtTokenDecoded = jwt.decode(jwtToken); | |||
| const refreshTokenDecoded = jwt.decode(refresh); | |||
| // If refresh token is expired, log out user | |||
| if (new Date() > new Date(refreshTokenDecoded?.exp * 1000)) { | |||
| dispatch(logoutUser()); | |||
| } | |||
| // If access token is expired, refresh access token | |||
| if (new Date() > new Date(jwtTokenDecoded.exp * 1000)) { | |||
| const axiosResponse = await axios.post(`${baseURL}auth/refresh`, { | |||
| token: refresh, | |||
| }); | |||
| const newToken = axiosResponse.data.token; | |||
| dispatch(refreshUserToken(newToken)); | |||
| } | |||
| return Promise.resolve(response); | |||
| }, accessTokensMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -2,6 +2,9 @@ import { ERROR_PAGE } from '../../constants/pages'; | |||
| import { attachPostRequestListener } from '../../request'; | |||
| import history from '../utils/history'; | |||
| //Interceptor unique name | |||
| export const serverErrorMiddlewareInterceptorName = "INTERNAL_SERVER_ERROR_MIDDLEWARE_INTERCEPTOR"; | |||
| export default () => (next) => (action) => { | |||
| attachPostRequestListener((error) => { | |||
| if (!error.response) { | |||
| @@ -11,7 +14,7 @@ export default () => (next) => (action) => { | |||
| return history.push(ERROR_PAGE); | |||
| } | |||
| return Promise.reject(error); | |||
| }); | |||
| }, serverErrorMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -5,37 +5,41 @@ import { | |||
| SUCCESS, | |||
| UPDATE, | |||
| SUBMIT, | |||
| } from '../actions/actionHelpers'; | |||
| } from "../actions/actionHelpers"; | |||
| const promiseTypes = [FETCH, UPDATE, DELETE, SUBMIT]; | |||
| export default ({ dispatch }) => (next) => (action) => { | |||
| const promiseType = promiseTypes.find((promiseType) => | |||
| action.type.includes(promiseType), | |||
| ); | |||
| if (promiseType) { | |||
| dispatch({ | |||
| type: 'UPDATE_LOADER', | |||
| payload: { | |||
| actionType: action.type.replace(promiseType, '[LOADING]'), | |||
| isLoading: true, | |||
| }, | |||
| }); | |||
| return next(action); | |||
| } | |||
| if (action.type.includes(SUCCESS) || action.type.includes(ERROR)) { | |||
| const actionType = action.type.includes(SUCCESS) | |||
| ? action.type.replace(SUCCESS, '[LOADING]') | |||
| : action.type.replace(ERROR, '[LOADING]'); | |||
| dispatch({ | |||
| type: 'UPDATE_LOADER', | |||
| payload: { | |||
| actionType, | |||
| isLoading: false, | |||
| }, | |||
| }); | |||
| return next(action); | |||
| } | |||
| next(action); | |||
| }; | |||
| export default ({ dispatch }) => | |||
| (next) => | |||
| (action) => { | |||
| const promiseType = promiseTypes.find((promiseType) => | |||
| action.type.includes(promiseType) | |||
| ); | |||
| if (promiseType) { | |||
| dispatch({ | |||
| type: "UPDATE_LOADER", | |||
| payload: { | |||
| actionType: action.type.replace(promiseType, "[LOADING]"), | |||
| isLoading: true, | |||
| }, | |||
| }); | |||
| return next(action); | |||
| } | |||
| if (action.type.includes(SUCCESS) || action.type.includes(ERROR)) { | |||
| const actionType = action.type.includes(SUCCESS) | |||
| ? action.type.replace(SUCCESS, "[LOADING]") | |||
| : action.type.replace(ERROR, "[LOADING]"); | |||
| dispatch({ | |||
| type: "UPDATE_LOADER", | |||
| payload: { | |||
| actionType, | |||
| isLoading: false, | |||
| }, | |||
| }); | |||
| return next(action); | |||
| } | |||
| next(action); | |||
| }; | |||
| @@ -2,6 +2,9 @@ import { attachPostRequestListener } from '../../request'; | |||
| import apiEndpoints from '../../request/apiEndpoints'; | |||
| import { logoutUser } from '../actions/login/loginActions'; | |||
| // Interceptor unique name | |||
| export const requestStatusMiddlewareInterceptorName = "REQUEST_STATUS_MIDDLEWARE_INTERCEPTOR"; | |||
| export default ({ dispatch }) => (next) => (action) => { | |||
| attachPostRequestListener((error) => { | |||
| if (!error.response) { | |||
| @@ -16,7 +19,7 @@ export default ({ dispatch }) => (next) => (action) => { | |||
| return dispatch(logoutUser()); | |||
| } | |||
| return Promise.reject(error); | |||
| }); | |||
| }, requestStatusMiddlewareInterceptorName); | |||
| next(action); | |||
| }; | |||
| @@ -14,6 +14,7 @@ const initialState = { | |||
| token: { | |||
| RefreshToken: '', | |||
| JwtToken: '', | |||
| userId: '' | |||
| }, | |||
| errorMessage: '', | |||
| }; | |||
| @@ -3,12 +3,14 @@ import { | |||
| OFFERS_CLEAR, | |||
| OFFERS_ERROR, | |||
| OFFERS_SET, | |||
| OFFER_ADD, | |||
| } from "../../actions/offers/offersActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| offers: [], | |||
| error: "", | |||
| newOffer: "", | |||
| }; | |||
| export default createReducer( | |||
| @@ -17,6 +19,7 @@ export default createReducer( | |||
| [OFFERS_CLEAR]: clearOffers, | |||
| [OFFERS_SET]: setOffers, | |||
| [OFFERS_ADD]: addOffers, | |||
| [OFFER_ADD]: addOffer, | |||
| }, | |||
| initialState | |||
| ); | |||
| @@ -40,3 +43,9 @@ function addOffers(state, action) { | |||
| offers: [...state.offers, ...action.payload] | |||
| } | |||
| } | |||
| function addOffer(state, action) { | |||
| return { | |||
| ...state, | |||
| offer: action.payload | |||
| } | |||
| } | |||
| @@ -1,17 +1,23 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import { | |||
| SET_USER, | |||
| SET_USER_ACCESS_TOKEN, | |||
| SET_USER_ERROR, | |||
| SET_USER_REFRESH_TOKEN, | |||
| } from '../../actions/user/userActionConstants'; | |||
| const initialState = { | |||
| user: {}, | |||
| accessToken: {}, | |||
| refreshToken: {}, | |||
| userId: {} | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [SET_USER]: setUser, | |||
| [SET_USER_ERROR]: setUserError, | |||
| [SET_USER_ACCESS_TOKEN]: setUserAccessToken, | |||
| [SET_USER_REFRESH_TOKEN]: setUserRefreshToken | |||
| }, | |||
| initialState, | |||
| ); | |||
| @@ -19,7 +25,7 @@ export default createReducer( | |||
| function setUser(state, action) { | |||
| return { | |||
| ...state, | |||
| user: action.payload, | |||
| userId: action.payload, | |||
| }; | |||
| } | |||
| @@ -29,3 +35,15 @@ function setUserError(state, action) { | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| function setUserAccessToken(state, action) { | |||
| return { | |||
| ...state, | |||
| accessToken: action.payload | |||
| } | |||
| } | |||
| function setUserRefreshToken(state, action) { | |||
| return { | |||
| ...state, | |||
| refreshToken: action.payload | |||
| } | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| // function* setFilters({ payload }) { | |||
| // } | |||
| @@ -20,7 +20,11 @@ function* forgotPassword({payload}) { | |||
| } | |||
| function* resetPassword({payload}) { | |||
| try { | |||
| const data = yield call(resetPasswordRequest) | |||
| const data = yield call(resetPasswordRequest, { | |||
| password: payload.password, | |||
| password2: payload.password2, | |||
| token: payload.token | |||
| }) | |||
| if (data) { | |||
| if (payload.handleResponseSuccess) { | |||
| yield call(payload.handleResponseSuccess); | |||
| @@ -6,27 +6,18 @@ import { | |||
| LOGIN_USER_FETCH, | |||
| LOGOUT_USER, | |||
| REFRESH_TOKEN, | |||
| GENERATE_TOKEN, | |||
| } from "../actions/login/loginActionConstants"; | |||
| import { | |||
| attemptLogin, | |||
| logoutUserRequest, | |||
| refreshTokenRequest, | |||
| generateTokenRequest, | |||
| } from "../../request/loginRequest"; | |||
| import { | |||
| fetchUserError, | |||
| fetchUserSuccess, | |||
| resetLoginState, | |||
| updateUserToken, | |||
| } from "../actions/login/loginActions"; | |||
| import { LOGIN_PAGE } from "../../constants/pages"; | |||
| import { setUser } from "../actions/user/userActions"; | |||
| import { addHeaderToken, removeHeaderToken } from "../../request"; | |||
| import { | |||
| IMPERSONATE_USER_UID, | |||
| REGISTRATION_USER_UID, | |||
| } from "../../constants/sessionStorage"; | |||
| import { | |||
| JWT_REFRESH_TOKEN, | |||
| JWT_TOKEN, | |||
| @@ -38,37 +29,46 @@ import { | |||
| authScopeRemoveHelper, | |||
| authScopeSetHelper, | |||
| } from "../../util/helpers/authScopeHelpers"; | |||
| // import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| import { setUserAccessToken } from "../actions/user/userActions"; | |||
| import i18next from "i18next"; | |||
| function* fetchUser({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptLogin, payload); | |||
| if (data.tokens) { | |||
| const token = data.tokens[data.tokens.length - 1].token.toString(); | |||
| // cemu sluzi? | |||
| const user = jwt.decode(token); | |||
| if (data.token) { | |||
| const token = data.token; | |||
| const refresh = data.refresh; | |||
| const tokenDecoded = jwt.decode(token); | |||
| const refreshDecoded = jwt.decode(refresh); | |||
| const accessToken = { | |||
| token: token, | |||
| exp: tokenDecoded.exp, | |||
| }; | |||
| const refreshToken = { | |||
| token: refresh, | |||
| exp: refreshDecoded.exp, | |||
| }; | |||
| const userId = tokenDecoded._id; | |||
| yield call(authScopeSetHelper, JWT_TOKEN, token); | |||
| // yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken); | |||
| // yield call(authScopeSetHelper, REFRESH_TOKEN_CONST, data.RefreshToken); | |||
| yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, refresh); | |||
| yield call(addHeaderToken, token); | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(fetchUserSuccess(data)); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| yield put(fetchUserSuccess({JwtToken: accessToken, RefreshToken: refreshToken, userId})); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| if (payload.handleApiResponseError) { | |||
| yield call(payload.handleApiResponseError); | |||
| yield call(payload.handleApiResponseError, e.response.status); | |||
| } | |||
| let errorMessage = "Greska!"; | |||
| if (e.response.status === 400) { | |||
| errorMessage = "Pogresan mail ili lozinka!"; | |||
| let errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| if (e.response.status === 401) { | |||
| errorMessage = i18next.t("login.wrongCredentials", { | |||
| lng: "rs" | |||
| }); | |||
| } | |||
| // const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(fetchUserError(errorMessage)); | |||
| } | |||
| } | |||
| @@ -87,11 +87,10 @@ function* authenticateUser() { | |||
| }) | |||
| ); | |||
| } catch (error) { | |||
| // const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| // yield put(fetchUserError(errorMessage)); | |||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| yield put(fetchUserError(errorMessage)); | |||
| yield call(authScopeRemoveHelper, JWT_TOKEN); | |||
| // yield call(authScopeRemoveHelper, JWT_REFRESH_TOKEN); | |||
| // yield call(authScopeRemoveHelper, REFRESH_TOKEN_CONST); | |||
| yield call(authScopeRemoveHelper, JWT_REFRESH_TOKEN); | |||
| } | |||
| } | |||
| @@ -99,9 +98,8 @@ function* logoutUser() { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| const user = jwt.decode(JwtToken); | |||
| console.log({token: JwtToken, userId: user._id}) | |||
| if (user) { | |||
| yield call(logoutUserRequest, {token: JwtToken, userId: user._id}); | |||
| yield call(logoutUserRequest, { token: JwtToken, userId: user._id }); | |||
| } | |||
| } catch (error) { | |||
| console.log(error); // eslint-disable-line | |||
| @@ -112,65 +110,15 @@ function* logoutUser() { | |||
| yield call(history.replace, LOGIN_PAGE); | |||
| } | |||
| } | |||
| export function* refreshToken() { | |||
| function* refreshUserToken({payload}) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| const JwtRefreshToken = yield call( | |||
| authScopeStringGetHelper, | |||
| JWT_REFRESH_TOKEN | |||
| ); | |||
| if (JwtToken && JwtRefreshToken) { | |||
| const { data } = yield call(refreshTokenRequest, { | |||
| JwtRefreshToken, | |||
| JwtToken, | |||
| }); | |||
| yield call(authScopeSetHelper, JWT_TOKEN, data.JwtToken); | |||
| yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken); | |||
| const user = jwt.decode(data.JwtToken); | |||
| addHeaderToken(data.JwtToken); | |||
| yield put(setUser(user)); | |||
| yield put(updateUserToken(data.JwtToken)); | |||
| } | |||
| } catch (error) { | |||
| yield call(logoutUser); | |||
| console.log(error); // eslint-disable-line | |||
| const newTokenDecoded = jwt.decode(payload); | |||
| yield put(setUserAccessToken({token: payload, exp: newTokenDecoded.exp})); | |||
| yield call(addHeaderToken, payload); | |||
| yield call(authScopeSetHelper, JWT_TOKEN, payload); | |||
| } | |||
| } | |||
| export function* generateToken({ payload }) { | |||
| try { | |||
| const { data } = yield call(generateTokenRequest, payload.data); | |||
| const { JwtToken, JwtRefreshToken } = data; | |||
| if (JwtToken && JwtRefreshToken) { | |||
| yield call(authScopeSetHelper, JWT_TOKEN, data.JwtToken); | |||
| yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken); | |||
| if (payload.impersonate) { | |||
| sessionStorage.setItem(IMPERSONATE_USER_UID, payload.accountUid); | |||
| } | |||
| if (payload.registration) { | |||
| sessionStorage.setItem(REGISTRATION_USER_UID, payload.accountUid); | |||
| } | |||
| const user = jwt.decode(data.JwtToken); | |||
| addHeaderToken(data.JwtToken); | |||
| if (user) { | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(updateUserToken(data.JwtToken)); | |||
| if (payload.onSuccess) { | |||
| yield call(payload.onSuccess); | |||
| } | |||
| } | |||
| } catch (error) { | |||
| yield call(logoutUser); | |||
| console.log(error); // eslint-disable-line | |||
| catch(e){ | |||
| console.log(e); | |||
| } | |||
| } | |||
| @@ -179,7 +127,6 @@ export default function* loginSaga() { | |||
| takeLatest(LOGIN_USER_FETCH, fetchUser), | |||
| takeLatest(AUTHENTICATE_USER, authenticateUser), | |||
| takeLatest(LOGOUT_USER, logoutUser), | |||
| takeLatest(REFRESH_TOKEN, refreshToken), | |||
| takeLatest(GENERATE_TOKEN, generateToken), | |||
| takeLatest(REFRESH_TOKEN, refreshUserToken) | |||
| ]); | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import { all, takeLatest, call, put } from "@redux-saga/core/effects"; | |||
| import { attemptFetchOffers } from "../../request/offersRequest"; | |||
| import { OFFERS_FETCH } from "../actions/offers/offersActionConstants"; | |||
| import { attemptAddOffer, attemptFetchOffers } from "../../request/offersRequest"; | |||
| import { OFFERS_FETCH, OFFER_ADD } from "../actions/offers/offersActionConstants"; | |||
| import { setOffers } from "../actions/offers/offersActions"; | |||
| function* fetchOffers() { | |||
| @@ -13,6 +13,18 @@ function* fetchOffers() { | |||
| } | |||
| } | |||
| function* createOffer(payload) { | |||
| try { | |||
| const data = yield call(attemptAddOffer, payload); | |||
| console.log(data); | |||
| } | |||
| catch (e) { | |||
| console.log(e); | |||
| } | |||
| } | |||
| export default function* offersSaga() { | |||
| yield all([takeLatest(OFFERS_FETCH, fetchOffers)]); | |||
| yield all([takeLatest(OFFERS_FETCH, fetchOffers), | |||
| takeLatest(OFFER_ADD, createOffer)]); | |||
| } | |||