| @@ -11,6 +11,8 @@ import { | |||
| FORGOT_PASSWORD_MAIL_SENT, | |||
| REGISTER_PAGE, | |||
| REGISTER_SUCCESSFUL_PAGE, | |||
| RESET_PASSWORD_PAGE, | |||
| CREATE_OFFER_PAGE, | |||
| } from './constants/pages'; | |||
| import LoginPage from './pages/LoginPage/LoginPageMUI'; | |||
| import HomePage from './pages/HomePage/HomePageMUI'; | |||
| @@ -21,6 +23,8 @@ import PrivateRoute from './components/Router/PrivateRoute'; | |||
| import MailSent from './pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent'; | |||
| import Register from './pages/RegisterPages/Register/Register'; | |||
| import RegisterSuccessful from './pages/RegisterPages/RegisterSuccessful.js/RegisterSuccessful'; | |||
| import ResetPasswordPage from './pages/ResetPasswordPage/ResetPasswordPage'; | |||
| import CreateOffer from './pages/CreateOffer/CreateOffer'; | |||
| const AppRoutes = () => { | |||
| @@ -34,6 +38,8 @@ const AppRoutes = () => { | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <Route path={FORGOT_PASSWORD_MAIL_SENT} component={MailSent} /> | |||
| <Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> | |||
| <Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage}/> | |||
| <Route path={CREATE_OFFER_PAGE} component={CreateOffer}/> | |||
| <PrivateRoute | |||
| exact | |||
| @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; | |||
| export const IconButton = (props) => { | |||
| return <IconButtonContainer style={props.containerStyle} className={props.className}> | |||
| <IconButtonStyled onClick={props.onClick} sx={props.style} iconColor={props.iconColor}> | |||
| <IconButtonStyled onClick={props.onClick} sx={props.style} iconcolor={props.iconColor}> | |||
| {props.children} | |||
| </IconButtonStyled> | |||
| </IconButtonContainer> | |||
| @@ -8,9 +8,9 @@ export const IconButtonStyled = styled(IconButton)` | |||
| height: ${props => props.height ? props.height : "36px"}; | |||
| width: ${props => props.width ? props.width : "36px"}; | |||
| padding: 0; | |||
| ${props => props.iconColor && ` | |||
| ${props => props.iconcolor && ` | |||
| & svg path { | |||
| stroke: ${props.iconColor}; | |||
| stroke: ${props.iconcolor}; | |||
| } | |||
| `} | |||
| ` | |||
| @@ -0,0 +1,183 @@ | |||
| /* 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 { | |||
| fetchUser, | |||
| } from "../../../store/actions/login/loginActions"; | |||
| 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 "../../MUI/BackdropComponent"; | |||
| import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors"; | |||
| import { LOGIN_USER_LOADING } from "../../../store/actions/login/loginActionConstants"; | |||
| import { TextField } from "../../TextFields/TextField/TextField"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import Link from "../../Link/Link"; | |||
| import { | |||
| CreateOfferContainer, | |||
| CreateOfferTitle, | |||
| CreateOfferFormContainer, | |||
| RegisterAltText, | |||
| RegisterTextContainer, | |||
| } from "./CreateOffer.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import StepProgress from "../../StepProgress/StepProgress"; | |||
| const CreateOffer = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| 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) | |||
| ); | |||
| const handleApiResponseSuccess = (status) => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| const { username: email, password: password } = values; | |||
| dispatch( | |||
| fetchUser({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| nameOfProduct: "", | |||
| description: "", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")), | |||
| description: Yup.string().required(t("login.descriptionRequired")).min(8), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <CreateOfferContainer> | |||
| <CreateOfferTitle component="h1" variant="h5"> | |||
| Nova Objava | |||
| </CreateOfferTitle> | |||
| <StepProgress current={1} numberOfSteps={3}/> | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <TextField | |||
| name="nameOfProduct" | |||
| placeholder={"Naziv proizvoda..."} | |||
| italicPlaceholder | |||
| margin="normal" | |||
| value={formik.values.nameOfProduct} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.nameOfProduct && formik.errors.nameOfProduct)} | |||
| helperText={formik.touched.nameOfProduct && formik.errors.nameOfProduct} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| name="description" | |||
| placeholder={"Opis..."} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.description && formik.errors.description)} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| multiline | |||
| minRows={4} | |||
| height={"100px"} | |||
| /> | |||
| <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> | |||
| </CreateOfferFormContainer> | |||
| </CreateOfferContainer> | |||
| ); | |||
| }; | |||
| CreateOffer.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default CreateOffer; | |||
| @@ -0,0 +1,56 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const CreateOfferContainer = styled(Container)` | |||
| margin-top: 0px; | |||
| display: flex; | |||
| width: 380px; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| `; | |||
| export const CreateOfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| width: 328px; | |||
| height: 33px; | |||
| text-align: center; | |||
| flex: 1; | |||
| font-style: normal; | |||
| font-weight: 700; | |||
| font-size: 24px; | |||
| line-height: 33px; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| margin-top: 36px; | |||
| margin-bottom: 40px; | |||
| `; | |||
| export const CreateOfferDescription = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| margin-top: 9px; | |||
| width: 221px; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| display: flex; | |||
| align-items: center; | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| `; | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 700px; | |||
| `; | |||
| export const RegisterAltText = styled(Typography)` | |||
| font-family: "Poppins"; | |||
| color: ${selectedTheme.primaryText}; | |||
| 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; | |||
| ` | |||
| @@ -50,9 +50,15 @@ const FilterCard = () => { | |||
| } | |||
| if (queryObject.city) { | |||
| let filters = []; | |||
| queryObject.city.forEach((item) => { | |||
| filters.push(Mockupdata[0].find((p) => p.string === item).id); | |||
| }); | |||
| if (Array.isArray(queryObject.city)) { | |||
| queryObject.city.forEach((item) => { | |||
| filters.push(Mockupdata[0].find((p) => p.string === item).id); | |||
| }); | |||
| } else { | |||
| filters.push( | |||
| Mockupdata[0].find((p) => p.string === queryObject.city).id | |||
| ); | |||
| } | |||
| setAppliedFilters([...filters]); | |||
| } | |||
| }, []); | |||
| @@ -5,11 +5,11 @@ import selectedTheme from "../../../themes"; | |||
| export const FilterCardContainer = styled(Box)` | |||
| border-radius: 0; | |||
| border-top-right-radius: 4px; | |||
| height: 100%; | |||
| height: calc(100% - 90px); | |||
| padding: 36px; | |||
| background-color: white; | |||
| width: 100%; | |||
| position: "fixed"; | |||
| width: calc(100% / 12 * 2.4); | |||
| position: fixed; | |||
| left: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| @@ -22,6 +22,7 @@ export const Title = styled(Typography)` | |||
| font-weight: 900; | |||
| font-family: "Open Sans"; | |||
| padding-left: 15px; | |||
| padding-top: 4px; | |||
| color: ${selectedTheme.primaryText}; | |||
| position: relative; | |||
| top: -12px; | |||
| @@ -46,5 +47,8 @@ export const Footer = styled(Box)` | |||
| } | |||
| `; | |||
| export const ContentContainer = styled(Box)` | |||
| overflow-y: auto; | |||
| margin-bottom: 10px; | |||
| ${() => window.scrollbars.visible && `padding-right: 15px;`} | |||
| ` | |||
| @@ -24,6 +24,9 @@ const FilterCheckboxDropdown = (props) => { | |||
| useEffect(() => { | |||
| setDataToShow([...data]); | |||
| }, []); | |||
| useEffect(() => { | |||
| console.log("props.filters: ", props.filters); | |||
| }, [props.filters]) | |||
| useEffect(() => { | |||
| if (toSearch.length > 0) { | |||
| @@ -34,6 +37,7 @@ const FilterCheckboxDropdown = (props) => { | |||
| ); | |||
| } else { | |||
| setDataToShow([...data]); | |||
| } | |||
| }, [toSearch]); | |||
| @@ -41,7 +45,7 @@ const FilterCheckboxDropdown = (props) => { | |||
| if (props.oneValueAllowed) { | |||
| props.setItemsSelected[item.id]; | |||
| } else { | |||
| if (props.filters.find((p) => p.id === item.id)) { | |||
| if (props.filters.includes(item.id)) { | |||
| props.setItemsSelected((itemsSelected) => [ | |||
| ...itemsSelected.filter((p) => p !== item.id), | |||
| ]); | |||
| @@ -117,7 +121,7 @@ const FilterCheckboxDropdown = (props) => { | |||
| leftText={item.string} | |||
| rightText={item.numberOfProducts} | |||
| value={item.id} | |||
| checked={props.filters.includes(item.id, 0)} | |||
| checked={props.filters.includes(item.id)} | |||
| onChange={() => handleChange(item)} | |||
| fullWidth | |||
| /> | |||
| @@ -4,6 +4,7 @@ import { | |||
| CheckButton, | |||
| DetailIcon, | |||
| DetailText, | |||
| Line, | |||
| MessageIcon, | |||
| OfferAuthor, | |||
| OfferAuthorName, | |||
| @@ -28,7 +29,7 @@ import selectedTheme from "../../../themes"; | |||
| const OfferCard = (props) => { | |||
| return ( | |||
| <OfferCardContainer sponsored={props.sponsored} halfWidth={props.halfWidth}> | |||
| <OfferCardContainer sponsored={props.sponsored ? 1 : 0} halfwidth={props.halfwidth ? 1 : 0}> | |||
| <OfferImage>{props.image}</OfferImage> | |||
| <OfferInfo> | |||
| <OfferTitle>{props.title}</OfferTitle> | |||
| @@ -43,7 +44,6 @@ const OfferCard = (props) => { | |||
| </DetailIcon> | |||
| <DetailText>{props.category}</DetailText> | |||
| </OfferCategory> | |||
| <OfferPackage> | |||
| <DetailIcon color="black" component="span" size="16px"> | |||
| <Quantity width={"12px"} height={"12px"} /> | |||
| @@ -58,8 +58,9 @@ const OfferCard = (props) => { | |||
| </OfferViews> | |||
| </OfferDetails> | |||
| </OfferInfo> | |||
| {!props.halfWidth ? ( | |||
| {!props.halfwidth ? ( | |||
| <React.Fragment> | |||
| <Line/> | |||
| <OfferDescription> | |||
| <OfferDescriptionTitle>Opis:</OfferDescriptionTitle> | |||
| <OfferDescriptionText>{props.description}</OfferDescriptionText> | |||
| @@ -107,12 +108,12 @@ OfferCard.propTypes = { | |||
| quantity: PropTypes.number, | |||
| package: PropTypes.string, | |||
| numberOfViews: PropTypes.number, | |||
| halfWidth: PropTypes.bool, | |||
| halfwidth: PropTypes.bool, | |||
| sponsored: PropTypes.bool, | |||
| }; | |||
| OfferCard.defaultProps = { | |||
| halfWidth: false, | |||
| sponsored: false, | |||
| halfwidth: false, | |||
| sponsored: true, | |||
| }; | |||
| export default OfferCard; | |||
| @@ -8,12 +8,15 @@ import { Icon } from "../../Icon/Icon"; | |||
| export const OfferCardContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| width: ${props => !props.halfWidth ? "100%" : "49%"}; | |||
| width: ${(props) => (!props.halfwidth ? "100%" : "49%")}; | |||
| box-sizing: border-box; | |||
| margin: 10px 0; | |||
| background-color: ${props => props.sponsored ? selectedTheme.backgroundSponsoredColor : "white"}; | |||
| border: 1px solid ${selectedTheme.borderColor}; | |||
| background-color: ${(props) => | |||
| props.sponsored ? selectedTheme.backgroundSponsoredColor : "white"}; | |||
| border-radius: 4px; | |||
| ${(props) => | |||
| props.sponsored && | |||
| `border: 1px solid ${selectedTheme.borderSponsoredColor};`} | |||
| padding: 16px; | |||
| max-width: 2000px; | |||
| height: 180px; | |||
| @@ -54,23 +57,24 @@ export const OfferLocation = styled(Typography)` | |||
| export const OfferDetails = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex-wrap: ${props => !props.halfWidth ? "no-wrap" : "wrap"}; | |||
| flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")}; | |||
| justify-content: space-between; | |||
| `; | |||
| export const OfferCategory = styled(Typography)` | |||
| export const OfferCategory = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 12px; | |||
| width: 33%; | |||
| `; | |||
| export const OfferPackage = styled(Typography)` | |||
| export const OfferPackage = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 12px; | |||
| width: 34%; | |||
| `; | |||
| export const OfferViews = styled(Typography)` | |||
| export const OfferViews = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| @@ -82,17 +86,29 @@ export const OfferDescriptionTitle = styled(Box)` | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| line-height: 16px; | |||
| padding-top: 20px; | |||
| `; | |||
| export const OfferDescriptionText = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| line-height: 22px; | |||
| max-width: 250px; | |||
| max-width: calc(100% - 230px); | |||
| max-height: 120px; | |||
| overflow: hidden; | |||
| display: -webkit-box; | |||
| -webkit-line-clamp: 5; | |||
| -webkit-box-orient: vertical; | |||
| `; | |||
| export const OfferDescription = styled(Box)` | |||
| flex: 3; | |||
| margin: auto 0; | |||
| padding-left: 35px; | |||
| `; | |||
| export const Line = styled(Box)` | |||
| border-left: 1px solid rgba(0, 0, 0, 0.15); | |||
| height: 100px; | |||
| width: 0; | |||
| margin: auto 0; | |||
| `; | |||
| export const DetailIcon = styled(Icon)` | |||
| & svg { | |||
| @@ -111,24 +127,24 @@ export const DetailText = styled(Typography)` | |||
| left: 3px; | |||
| `; | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 180px; | |||
| height: 48px; | |||
| position: absolute; | |||
| bottom: 9px; | |||
| right: 9px; | |||
| &:hover button { | |||
| background-color: ${selectedTheme.primaryPurple} !important; | |||
| color: white !important; | |||
| } | |||
| ` | |||
| width: 180px; | |||
| height: 48px; | |||
| position: absolute; | |||
| bottom: 9px; | |||
| right: 9px; | |||
| &:hover button { | |||
| background-color: ${selectedTheme.primaryPurple} !important; | |||
| color: white !important; | |||
| } | |||
| `; | |||
| export const MessageIcon = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| position: absolute; | |||
| top: 10px; | |||
| right: 10px; | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| ` | |||
| width: 40px; | |||
| height: 40px; | |||
| position: absolute; | |||
| top: 10px; | |||
| right: 10px; | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| `; | |||
| @@ -23,7 +23,7 @@ export const CheckBox = (props) => { | |||
| className={props.className} | |||
| > | |||
| <FormControlLabelStyled | |||
| fullWidth={props.fullWidth} | |||
| fullwidth={props.fullWidth ? 1 : 0} | |||
| control={ | |||
| <CheckBoxStyled | |||
| sx={props.checkBoxStyle} | |||
| @@ -17,7 +17,7 @@ export const CheckBoxStyled = styled(Checkbox)` | |||
| `; | |||
| export const FormControlLabelStyled = styled(FormControlLabel)` | |||
| ${(props) => | |||
| props.fullWidth && | |||
| props.fullwidth && | |||
| ` | |||
| width: 100%; | |||
| display: flex; | |||
| @@ -15,7 +15,7 @@ import PropTypes from "prop-types"; | |||
| const DropdownList = (props) => { | |||
| const [listShown, setListShown] = useState(props.defaultOpen); | |||
| return ( | |||
| <DropdownListContainer fullWidth={props.fullWidth}> | |||
| <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | |||
| <DropdownHeader> | |||
| {props.dropdownIcon && ( | |||
| <DropdownIcon onClick={() => setListShown((prevState) => !prevState)}> | |||
| @@ -5,7 +5,7 @@ import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| export const DropdownListContainer = styled(Box)` | |||
| width: ${(props) => | |||
| props.fullWidth ? "100%" : props.width ? props.width : "250px"}; | |||
| props.fullwidth ? "100%" : props.width ? props.width : "250px"}; | |||
| padding: 8px 0; | |||
| `; | |||
| @@ -49,7 +49,7 @@ DialogComponent.propTypes = { | |||
| open: PropTypes.bool.isRequired, | |||
| content: PropTypes.any, | |||
| onClose: PropTypes.func.isRequired, | |||
| maxWidth: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), | |||
| maxWidth: PropTypes.any, | |||
| fullWidth: PropTypes.bool, | |||
| responsive: PropTypes.bool, | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| HeaderButton, | |||
| @@ -14,6 +14,9 @@ import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-gri | |||
| 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"; | |||
| const DownArrow = (props) => ( | |||
| <IconStyled {...props}> | |||
| @@ -37,19 +40,57 @@ const MockupdataForSelect = [ | |||
| ]; | |||
| const Header = (props) => { | |||
| const category = "Gradjevinski materijal"; | |||
| const city = "Nis"; | |||
| const locationString = category + " | " + city; | |||
| const [categoryString, setCategoryString] = useState(""); | |||
| const [filtersString, setFiltersString] = useState(""); | |||
| const { category, cities } = useSelector(selectFilters); | |||
| useEffect(() => { | |||
| let categorystring = ""; | |||
| if (category) { | |||
| categorystring = Mockupdata[1].find( | |||
| (item) => item.id === category | |||
| ).string; | |||
| } else { | |||
| categorystring = "Sve kategorije"; | |||
| } | |||
| let filtersstring = " | "; | |||
| if (cities) { | |||
| cities.forEach((item) => { | |||
| filtersstring = filtersstring.concat( | |||
| Mockupdata[0].find((p) => p.id === item).string + ", " | |||
| ); | |||
| }); | |||
| filtersstring = filtersstring.substring(0, filtersstring.length - 2); | |||
| } | |||
| console.log("categorysstring: ", categorystring); | |||
| console.log(filtersstring); | |||
| setCategoryString(categorystring); | |||
| setFiltersString(filtersstring); | |||
| }, [category, cities]); | |||
| return ( | |||
| <HeaderContainer> | |||
| <HeaderLocation>{locationString}</HeaderLocation> | |||
| <HeaderLocation>{categoryString + filtersString} </HeaderLocation> | |||
| <HeaderOptions> | |||
| <HeaderButtons> | |||
| <HeaderButton iconColor={props.isGrid ? selectedTheme.iconStrokeColor : selectedTheme.primaryPurple}> | |||
| <GridLine onClick={() => props.setIsGrid(false)}/> | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.iconStrokeColor | |||
| : selectedTheme.primaryPurple | |||
| } | |||
| onClick={() => props.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| <HeaderButton iconColor={props.isGrid ? selectedTheme.primaryPurple : selectedTheme.iconStrokeColor}> | |||
| <GridSquare onClick={() => props.setIsGrid(true)}/> | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.iconStrokeColor | |||
| } | |||
| onClick={() => props.setIsGrid(true)} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| </HeaderButtons> | |||
| <HeaderSelect defaultValue={0} IconComponent={DownArrow}> | |||
| @@ -72,9 +113,11 @@ Header.propTypes = { | |||
| children: PropTypes.node, | |||
| setIsGrid: PropTypes.func, | |||
| isGrid: PropTypes.bool, | |||
| filters: PropTypes.array, | |||
| category: PropTypes.string, | |||
| }; | |||
| Header.defaultProps = { | |||
| isGrid: false, | |||
| } | |||
| }; | |||
| export default Header; | |||
| @@ -6,9 +6,10 @@ import Offers from "./Offers/Offers"; | |||
| const MarketPlace = () => { | |||
| const [isGrid, setIsGrid] = useState(false); | |||
| return ( | |||
| <MarketPlaceContainer> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid}/> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} /> | |||
| <Offers isGrid={isGrid} /> | |||
| </MarketPlaceContainer> | |||
| ); | |||
| @@ -1,25 +1,22 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { OffersContainer } from './Offers.styled' | |||
| import MockupdataOffers from '../MockupdataOffers' | |||
| import OfferCard from '../../Cards/OfferCard/OfferCard' | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { OffersContainer } from "./Offers.styled"; | |||
| import MockupdataOffers from "../MockupdataOffers"; | |||
| import OfferCard from "../../Cards/OfferCard/OfferCard"; | |||
| const Offers = (props) => { | |||
| return ( | |||
| <OffersContainer> | |||
| {MockupdataOffers.map((item) => { | |||
| console.log(item); | |||
| return ( | |||
| <OfferCard {...item} key={item.id} halfWidth={props.isGrid} /> | |||
| ) | |||
| })} | |||
| {MockupdataOffers.map((item) => { | |||
| return <OfferCard {...item} key={item.id} halfwidth={props.isGrid} />; | |||
| })} | |||
| </OffersContainer> | |||
| ) | |||
| } | |||
| ); | |||
| }; | |||
| Offers.propTypes = { | |||
| children: PropTypes.node, | |||
| isGrid: PropTypes.bool, | |||
| } | |||
| children: PropTypes.node, | |||
| isGrid: PropTypes.bool, | |||
| }; | |||
| export default Offers | |||
| export default Offers; | |||
| @@ -11,10 +11,10 @@ import { Label } from "../../CheckBox/Label"; | |||
| const RadioButton = (props) => { | |||
| return ( | |||
| <RadioButtonContainer fullWidth={props.fullWidth}> | |||
| <RadioButtonContainer fullwidth={props.fullWidth ? 1 : 0}> | |||
| <FormControlLabelStyled | |||
| value={props.value} | |||
| fullWidth={props.fullWidth} | |||
| fullwidth={props.fullWidth ? 1 : 0} | |||
| control={ | |||
| <RadioButtonStyled | |||
| icon={<RadioUnchecked />} | |||
| @@ -3,7 +3,7 @@ import styled from "styled-components"; | |||
| export const RadioButtonContainer = styled(Box)` | |||
| ${(props) => | |||
| props.fullWidth && | |||
| props.fullwidth && | |||
| ` | |||
| width: 100%; | |||
| display: flex; | |||
| @@ -19,7 +19,7 @@ export const RadioButtonStyled = styled(Radio)` | |||
| `; | |||
| export const FormControlLabelStyled = styled(FormControlLabel)` | |||
| ${(props) => | |||
| props.fullWidth && | |||
| props.fullwidth && | |||
| ` | |||
| width: 100%; | |||
| display: flex; | |||
| @@ -29,6 +29,8 @@ export const TextField = (props) => { | |||
| value={props.value} | |||
| onChange={props.onChange} | |||
| error={props.error} | |||
| multiline={props.multiline} | |||
| minRows={props.minRows} | |||
| // helperText={props.helperText} | |||
| autoFocus={props.autoFocus} | |||
| fullWidth={props.fullWidth} | |||
| @@ -39,7 +41,7 @@ export const TextField = (props) => { | |||
| sx={props.style} | |||
| label={props.showAnimation ? props.placeholder : ""} | |||
| italic={props.italicPlaceholder && isFieldEmpty} | |||
| italicplaceholder={props.italicPlaceholder && isFieldEmpty} | |||
| ref={textInputRef} | |||
| focused={props.focused} | |||
| > | |||
| @@ -78,6 +80,8 @@ TextField.propTypes = { | |||
| textsize: PropTypes.string, | |||
| font: PropTypes.string, | |||
| ref: PropTypes.any, | |||
| minRows: PropTypes.number, | |||
| multiline: PropTypes.bool, | |||
| focused: PropTypes.bool, | |||
| InputProps: PropTypes.shape({ | |||
| startAdornment: PropTypes.node, | |||
| @@ -4,7 +4,7 @@ import selectedTheme from "../../../themes"; | |||
| export const TextFieldContainer = styled(Box)` | |||
| width: 100%; | |||
| height: ${props => props.height}; | |||
| height: ${(props) => props.height}; | |||
| box-sizing: border-box; | |||
| margin: 16px 0; | |||
| padding-left: 0; | |||
| @@ -13,19 +13,33 @@ export const TextFieldContainer = styled(Box)` | |||
| export const TextFieldStyled = styled(TextField)` | |||
| background-color: ${selectedTheme.primaryBackgroundColor}; | |||
| width: ${(props) => props.width}; | |||
| font-style: ${(props) => (props.italic === true ? "italic" : "normal")}; | |||
| font-style: ${(props) => | |||
| props.italicplaceholder === true ? "italic" : "normal"}; | |||
| padding-left: 0; | |||
| margin: 0; | |||
| padding: 0; | |||
| height: ${props => props.height}; | |||
| height: ${(props) => props.height}; | |||
| box-sizing: border-box; | |||
| & div { | |||
| padding-left: 2px; | |||
| ${(props) => | |||
| props.multiline && | |||
| ` | |||
| padding: 10px 16px; | |||
| max-height: ${props.height}; | |||
| overflow-y: auto; | |||
| & fieldset { | |||
| border: 1px solid rgba(0, 0, 0, 0.23); | |||
| border-radius: 4px; | |||
| } | |||
| `} | |||
| } | |||
| & div input { | |||
| height: ${props => props.height}; | |||
| height: ${(props) => props.height}; | |||
| box-sizing: border-box; | |||
| font-size: ${props => props.textsize ? props.textsize : "16px"} !important; | |||
| font-family: ${props => props.font ? props.font : ""} | |||
| font-size: ${(props) => | |||
| props.textsize ? props.textsize : "16px"} !important; | |||
| font-family: ${(props) => (props.font ? props.font : "")}; | |||
| } | |||
| `; | |||
| @@ -6,4 +6,6 @@ export const ERROR_PAGE = '/error-page'; | |||
| 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 REGISTER_SUCCESSFUL_PAGE = "/register/success"; | |||
| export const RESET_PASSWORD_PAGE = "/reset-password" | |||
| export const CREATE_OFFER_PAGE = "/create-offer" | |||
| @@ -0,0 +1,3 @@ | |||
| export const SESSION_STORAGE_SCOPE = "AUTH"; | |||
| export const IMPERSONATE_USER_UID = "IMPERSONATE_USER_UID"; | |||
| export const REGISTRATION_USER_UID = "REGISTRATION_USER_UID"; | |||
| @@ -103,6 +103,13 @@ export default { | |||
| label: 'Obnovite lozinku', | |||
| }, | |||
| }, | |||
| resetPassword: { | |||
| title: "Unesite novu lozinku", | |||
| description: "Poslali ste zahtev za promenu lozinke, molimo Vas da unesete novu željenu lozinku", | |||
| passwordLabel: "Nova Lozinka", | |||
| passwordConfirmLabel: "Potvrdite Lozinku", | |||
| buttonText: "Postavi lozinku" | |||
| }, | |||
| filters: { | |||
| title: "Filteri", | |||
| cancel: "Poništi filtere", | |||
| @@ -5,9 +5,9 @@ import { Grid } from "@mui/material"; | |||
| const GridLayout = (props) => { | |||
| return ( | |||
| <GridLayoutContainer maxWidth={true}> | |||
| <GridLayoutContainer> | |||
| {props.children} | |||
| <Grid container lg={12} maxHeight="lg"> | |||
| <Grid container maxHeight="lg"> | |||
| <LeftCard item xs={2} lg={2.5} xl={2.4} md={3}> | |||
| {props.leftCard} | |||
| </LeftCard> | |||
| @@ -1,7 +1,7 @@ | |||
| import { Box, Container, Grid } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const ProfileLayoutContainer = styled(Container)` | |||
| export const GridLayoutContainer = styled(Container)` | |||
| padding-left: 0; | |||
| padding-right: 0; | |||
| margin: 0; | |||
| @@ -5,9 +5,9 @@ import { Grid } from "@mui/material"; | |||
| const MainLayout = (props) => { | |||
| return ( | |||
| <MainLayoutContainer maxWidth={true}> | |||
| <MainLayoutContainer> | |||
| {props.children} | |||
| <Grid container lg={12} maxHeight="lg"> | |||
| <Grid container maxHeight="xl"> | |||
| <LeftCard item xs={2} lg={3} xl={2.4} md={4} > | |||
| {props.leftCard} | |||
| </LeftCard> | |||
| @@ -6,6 +6,7 @@ export const MainLayoutContainer = styled(Container)` | |||
| padding-right: 0; | |||
| margin: 0; | |||
| width: 100%; | |||
| max-width: none; | |||
| display: flex; | |||
| flex: 1; | |||
| height: 100%; | |||
| @@ -5,9 +5,9 @@ import { Grid } from "@mui/material"; | |||
| const ProfileLayout = (props) => { | |||
| return ( | |||
| <ProfileLayoutContainer maxWidth={true}> | |||
| <ProfileLayoutContainer> | |||
| {props.children} | |||
| <Grid container lg={12} maxHeight="lg"> | |||
| <Grid container maxHeight="xl"> | |||
| <LeftCard item xs={2} lg={2.5} xl={2.4} md={3}> | |||
| {props.leftCard} | |||
| </LeftCard> | |||
| @@ -0,0 +1,26 @@ | |||
| /* eslint-disable */ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CreateOfferContainer, | |||
| } from "./CreateOffer.styled"; | |||
| import CreateOffer from "../../components/Cards/CreateOfferCard/CreateOffer"; | |||
| const CreateOfferPage = ({ history }) => { | |||
| return ( | |||
| <CreateOfferContainer> | |||
| <CreateOffer/> | |||
| </CreateOfferContainer> | |||
| ); | |||
| }; | |||
| CreateOfferPage.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default CreateOfferPage; | |||
| @@ -0,0 +1,56 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../themes"; | |||
| export const CreateOfferContainer = styled(Container)` | |||
| margin-top: 0px; | |||
| display: flex; | |||
| width: 380px; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| `; | |||
| export const CreateOfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| width: 328px; | |||
| height: 33px; | |||
| text-align: center; | |||
| flex: 1; | |||
| font-style: normal; | |||
| font-weight: 700; | |||
| font-size: 24px; | |||
| line-height: 33px; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| margin-top: 36px; | |||
| margin-bottom: 40px; | |||
| `; | |||
| export const CreateOfferDescription = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| margin-top: 9px; | |||
| width: 221px; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| display: flex; | |||
| align-items: center; | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| `; | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 216px; | |||
| `; | |||
| export const RegisterAltText = styled(Typography)` | |||
| font-family: "Poppins"; | |||
| color: ${selectedTheme.primaryText}; | |||
| 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; | |||
| ` | |||
| @@ -12,20 +12,28 @@ import { | |||
| } from "./MailSent.styled"; | |||
| import { ReactComponent as MailSentImage } from "../../../assets/images/svg/mail-sent.svg"; | |||
| import { PrimaryButton } from "../../../components/Buttons/PrimaryButton/PrimaryButton"; | |||
| import { NavLink, useHistory } from "react-router-dom"; | |||
| import { NavLink, useHistory, useLocation } from "react-router-dom"; | |||
| import Link from "../../../components/Link/Link"; | |||
| import { Trans, useTranslation } from "react-i18next"; | |||
| import { LOGIN_PAGE } from "../../../constants/pages"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { forgotPassword } from "../../../store/actions/user/userActions"; | |||
| const MailSent = () => { | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| const location = useLocation(); | |||
| const dispatch = useDispatch(); | |||
| const navigateLogin = () => { | |||
| history.replace(LOGIN_PAGE); | |||
| }; | |||
| const handleResend = () => { | |||
| dispatch(forgotPassword(location.state.email)) | |||
| } | |||
| return ( | |||
| <MailSentContainer> | |||
| @@ -59,7 +67,7 @@ const MailSent = () => { | |||
| {t("forgotPassword.notRecievedMail")} | |||
| </StandardText> | |||
| <Link to="#" component={NavLink} underline="hover" align="center" textsize="16px"> | |||
| <Link to="#" component={NavLink} underline="hover" align="center" textsize="16px" onClick={handleResend}> | |||
| {t("common.sendAgain")} | |||
| </Link> | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useState } from "react"; | |||
| import { useFormik } from "formik"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import * as Yup from "yup"; | |||
| @@ -15,6 +15,8 @@ import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryBut | |||
| 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() | |||
| @@ -25,9 +27,24 @@ const forgotPasswordValidationSchema = Yup.object().shape({ | |||
| const ForgotPasswordPage = () => { | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const [emailNotFoundStatus, setEmailNotFoundStatus] = useState(false); | |||
| const handleSubmit = () => { | |||
| history.push(FORGOT_PASSWORD_MAIL_SENT); | |||
| const handleResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: FORGOT_PASSWORD_MAIL_SENT, | |||
| state: {email: formik.values.email} | |||
| }); | |||
| setEmailNotFoundStatus(true); | |||
| } | |||
| // const handleResponseError = () => { | |||
| // setEmailNotFoundStatus(true); | |||
| // console.log("greska!"); | |||
| // } | |||
| const handleSubmit = (values) => { | |||
| // validate email | |||
| dispatch(forgotPassword({email: values.email, handleResponseSuccess, handleResponseError: handleResponseSuccess})); | |||
| }; | |||
| const formik = useFormik({ | |||
| @@ -60,7 +77,7 @@ const ForgotPasswordPage = () => { | |||
| placeholder={t("common.labelEmail")} | |||
| margin="normal" | |||
| value={formik.values.email} | |||
| error={formik.touched.email && Boolean(formik.errors.email)} | |||
| error={(formik.touched.email && Boolean(formik.errors.email)) || emailNotFoundStatus} | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| onChange={formik.handleChange} | |||
| autoFocus | |||
| @@ -6,6 +6,7 @@ export const HomePageContainer = styled(Container)` | |||
| margin: 0; | |||
| height: 100%; | |||
| width: 100%; | |||
| max-width: none; | |||
| flex: 1; | |||
| display: flex; | |||
| flex-direction: column; | |||
| @@ -1,23 +1,59 @@ | |||
| import React from "react"; | |||
| // import { Box } from "@mui/material"; | |||
| import React, { useEffect } from "react"; | |||
| import Navbar from "../../components/MUI/NavbarComponent"; | |||
| //import Modals from '../../components/MUI/Examples/ModalsExample'; | |||
| //import DataGrid from '../../components/MUI/Examples/DataGridExample'; | |||
| //import PagingSortingFiltering from '../../components/MUI/Examples/PagingSortingFilteringExample'; | |||
| //import PagingSortingFilteringServerSide from '../../components/MUI/Examples/PagingSortingFilteringExampleServerSide'; | |||
| //import RandomDataProvider from '../../context/RandomDataContext'; | |||
| // import { GridStyled } from "./HomePage.styled"; | |||
| // import HomeListCard from "../../components/Cards/HomeListCard/HomeListCard"; | |||
| import FilterCard from "../../components/Cards/FilterCard/FilterCard"; | |||
| import { HomePageContainer } from "./HomePage.styled"; | |||
| import MarketPlace from "../../components/MarketPlace/MarketPlace"; | |||
| import MainLayout from "../../layouts/MainLayout/MainLayout"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { logoutUser } from "../../store/actions/login/loginActions"; | |||
| import Mockupdata from "../../components/Cards/FilterCard/Mockupdata"; | |||
| import qs from "query-string"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { setFilters } from "../../store/actions/filters/filtersActions"; | |||
| const HomePage = () => { | |||
| const dispatch = useDispatch(); | |||
| const history = useHistory(); | |||
| useEffect(() => { | |||
| 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.city) { | |||
| if (Array.isArray(queryObject.city)) { | |||
| queryObject.city.forEach((item) => { | |||
| cities.push(Mockupdata[0].find((p) => p.string === item).id); | |||
| }); | |||
| } else { | |||
| cities.push( | |||
| Mockupdata[0].find((p) => p.string === queryObject.city).id | |||
| ); | |||
| } | |||
| } | |||
| let subcategory = null; | |||
| if (queryObject.subcategory) { | |||
| subcategory = Mockupdata[1].find( | |||
| (item) => item.string === queryObject.subcategory.toString() | |||
| ).id; | |||
| } | |||
| console.log("iz useeffect: ", { category, subcategory, cities }); | |||
| dispatch(setFilters({ category, subcategory, cities })); | |||
| }, [history.location.search]); | |||
| const handleCl = () => { | |||
| dispatch(logoutUser()); | |||
| }; | |||
| return ( | |||
| <HomePageContainer maxWidth={true}> | |||
| <HomePageContainer> | |||
| <button onClick={handleCl}>Dugme</button> | |||
| <Navbar /> | |||
| <MainLayout leftCard={<FilterCard />} content={<MarketPlace />}/> | |||
| <MainLayout leftCard={<FilterCard />} content={<MarketPlace />} /> | |||
| {/* <Box sx={{ mt: 4, mx: 4 }}> | |||
| <GridStyled container justifyContent="space-between"> | |||
| <GridStyled item xs={12} md={3}> | |||
| @@ -12,8 +12,8 @@ import { | |||
| } 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.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye-striked.svg"; | |||
| 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"; | |||
| @@ -56,7 +56,11 @@ const LoginPage = ({ history }) => { | |||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | |||
| ); | |||
| const handleApiResponseSuccess = () => { | |||
| useEffect(() => { | |||
| dispatch(clearLoginErrors()); | |||
| }, []); | |||
| const handleApiResponseSuccess = (status) => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| @@ -66,12 +70,12 @@ const LoginPage = ({ history }) => { | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| const { username: Username, password: Password } = values; | |||
| const { username: email, password: password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchUser({ | |||
| Username, | |||
| Password, | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| @@ -84,7 +88,7 @@ const LoginPage = ({ history }) => { | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| username: Yup.string().required(t("login.usernameRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")).min(8), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| @@ -112,7 +116,7 @@ const LoginPage = ({ history }) => { | |||
| margin="normal" | |||
| value={formik.values.username} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.username && Boolean(formik.errors.username)} | |||
| error={(formik.touched.password && formik.errors.password) || error.length > 0} | |||
| helperText={formik.touched.username && formik.errors.username} | |||
| autoFocus | |||
| fullWidth | |||
| @@ -125,7 +129,7 @@ const LoginPage = ({ history }) => { | |||
| type={showPassword ? "text" : "password"} | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.password && Boolean(formik.errors.password)} | |||
| error={(formik.touched.password && formik.errors.password) || error.length > 0} | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth={true} | |||
| InputProps={{ | |||
| @@ -167,7 +171,6 @@ const LoginPage = ({ history }) => { | |||
| </PrimaryButton> | |||
| <RegisterTextContainer> | |||
| <RegisterAltText> | |||
| {t("login.dontHaveAccount").padEnd(2, " ")} | |||
| </RegisterAltText> | |||
| @@ -180,11 +183,8 @@ const LoginPage = ({ history }) => { | |||
| > | |||
| {t("login.signUp")} | |||
| </Link> | |||
| </RegisterTextContainer> | |||
| </LoginFormContainer> | |||
| </LoginPageContainer> | |||
| ); | |||
| }; | |||
| @@ -16,8 +16,18 @@ import selectedTheme from "../../../../themes"; | |||
| const FirstPartOfRegistration = (props) => { | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const [emailTakenStatus, setEmailTakenStatus] = useState(false); | |||
| const { t } = useTranslation(); | |||
| const handleForm = (values) => { | |||
| // validate email | |||
| if (true) { // eslint-disable-line | |||
| props.handleSubmit(values) | |||
| } else { | |||
| setEmailTakenStatus(true); | |||
| } | |||
| } | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| mail: "", | |||
| @@ -25,9 +35,9 @@ const FirstPartOfRegistration = (props) => { | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| mail: Yup.string().email().required(t("login.usernameRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")), | |||
| password: Yup.string().required(t("login.passwordRequired")).min(8), | |||
| }), | |||
| onSubmit: props.handleSubmit, | |||
| onSubmit: handleForm, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| @@ -49,7 +59,7 @@ const FirstPartOfRegistration = (props) => { | |||
| onChange={(value) => | |||
| formik.setFieldValue("mail", value.target.value) | |||
| } | |||
| error={formik.touched.mail && Boolean(formik.errors.mail)} | |||
| error={(formik.touched.mail && Boolean(formik.errors.mail)) || emailTakenStatus} | |||
| helperText={formik.touched.mail && formik.errors.mail} | |||
| autoFocus | |||
| fullWidth | |||
| @@ -65,12 +75,12 @@ const FirstPartOfRegistration = (props) => { | |||
| formik.setFieldValue("password", value.target.value) | |||
| } | |||
| error={ | |||
| formik.touched.passwordRegistration && | |||
| Boolean(formik.errors.passwordRegistration) | |||
| formik.touched.password && | |||
| Boolean(formik.errors.password) | |||
| } | |||
| helperText={ | |||
| formik.touched.passwordRegistration && | |||
| formik.errors.passwordRegistration | |||
| formik.touched.password && | |||
| formik.errors.password | |||
| } | |||
| fullWidth | |||
| InputProps={{ | |||
| @@ -11,28 +11,35 @@ import { | |||
| RegisterTitle, | |||
| } from "./Register.styled"; | |||
| import { ReactComponent as Logo } from "../../../assets/images/svg/logo-vertical.svg"; | |||
| import { NavLink, useHistory } from "react-router-dom"; | |||
| import { NavLink } from "react-router-dom"; | |||
| import { Trans, useTranslation } from "react-i18next"; | |||
| import Link from "../../../components/Link/Link"; | |||
| import StepProgress from "../../../components/StepProgress/StepProgress"; | |||
| import { REGISTER_SUCCESSFUL_PAGE } from "../../../constants/pages"; | |||
| // import { REGISTER_SUCCESSFUL_PAGE } from "../../../constants/pages"; | |||
| import FirstPartOfRegistration from "./FirstPart/FirstPartOfRegistration"; | |||
| import SecondPartOfRegistration from "./SecondPart/SecondPartOfRegistration"; | |||
| import ThirdPartOfRegistration from "./ThirdPart/ThirdPartOfRegistration"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { fetchRegisterUser } from "../../../store/actions/register/registerActions"; | |||
| const Register = () => { | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| // const history = useHistory(); | |||
| const dispatch = useDispatch(); | |||
| const [currentStep, setCurrentStep] = useState(1); | |||
| const [informations, setInformations] = useState({}); | |||
| const registerUser = (values) => { | |||
| dispatch(fetchRegisterUser(values)); | |||
| } | |||
| const handleSubmit = (values) => { | |||
| setInformations({ ...informations, ...values }); | |||
| if (currentStep !== 3) { | |||
| setCurrentStep((prevState) => prevState + 1); | |||
| } else { | |||
| history.push(REGISTER_SUCCESSFUL_PAGE); | |||
| registerUser({...informations, ...values}); | |||
| } | |||
| setInformations({ ...informations, ...values }); | |||
| }; | |||
| return ( | |||
| <RegisterPageContainer> | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| FormContainer, | |||
| @@ -13,6 +13,16 @@ import selectedTheme from "../../../../themes"; | |||
| const SecondPartOfRegistration = (props) => { | |||
| const { t } = useTranslation(); | |||
| const [PIBTakenStatus, setPIBTakenStatus] = useState(false); | |||
| const handleForm = (values) => { | |||
| // validate email | |||
| if (true) { // eslint-disable-line | |||
| props.handleSubmit(values) | |||
| } else { | |||
| setPIBTakenStatus(true); | |||
| } | |||
| } | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| @@ -21,9 +31,9 @@ const SecondPartOfRegistration = (props) => { | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| nameOfFirm: Yup.string().required(t("login.usernameRequired")), | |||
| PIB: Yup.number().required(t("login.passwordRequired")), | |||
| PIB: Yup.number().required(t("login.passwordRequired")).min(100000000).max(999999999), | |||
| }), | |||
| onSubmit: props.handleSubmit, | |||
| onSubmit: handleForm, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| @@ -52,7 +62,7 @@ const SecondPartOfRegistration = (props) => { | |||
| type="number" | |||
| value={formik.values.PIB} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.PIB && Boolean(formik.errors.PIB)} | |||
| error={(formik.touched.PIB && Boolean(formik.errors.PIB)) || PIBTakenStatus} | |||
| helperText={formik.touched.PIB && formik.errors.PIB} | |||
| fullWidth={true} | |||
| /> | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| FormContainer, | |||
| @@ -13,6 +13,16 @@ 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: { | |||
| @@ -27,7 +37,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: props.handleSubmit, | |||
| onSubmit: handleForm, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| @@ -45,7 +55,7 @@ const ThirdPartOfRegistration = (props) => { | |||
| type="number" | |||
| value={formik.values.phoneNumber} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)} | |||
| error={(formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)) || phoneNumberTakenStatus} | |||
| helperText={formik.touched.phoneNumber && formik.errors.phoneNumber} | |||
| autoFocus | |||
| fullWidth | |||
| @@ -0,0 +1,150 @@ | |||
| import React, { useState } from "react"; | |||
| import { useFormik } from "formik"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import * as Yup from "yup"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { | |||
| ResetPasswordPageContainer, | |||
| ResetPasswordDescription, | |||
| ResetPasswordTitle, | |||
| FormContainer, | |||
| Footer, | |||
| FooterText, | |||
| } 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 selectedTheme from "../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| 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"; | |||
| const ResetPasswordPage = () => { | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const [showPasswordConfirm, setShowPasswordConfirm] = useState(false); | |||
| const handleClickShowPassword = () => { | |||
| setShowPassword((prevState) => !prevState); | |||
| }; | |||
| const handleClickShowPasswordConfirm = () => { | |||
| setShowPasswordConfirm((prevState) => !prevState); | |||
| }; | |||
| const handleResponseSuccess = () => { | |||
| history.push(LOGIN_PAGE); | |||
| } | |||
| const handleResponseError = () => { | |||
| console.log("error"); | |||
| } | |||
| const handleSubmit = (values) => { | |||
| // validate email | |||
| dispatch( | |||
| resetPassword({ | |||
| password: values.password, | |||
| password2: values.passwordConfirm, | |||
| handleResponseSuccess, | |||
| handleResponseError | |||
| }) | |||
| ); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| password: "", | |||
| passwordConfirm: "", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| password: Yup.string().required().min(8), | |||
| passwordConfirm: Yup.string().oneOf([Yup.ref("password"), null]), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| return ( | |||
| <ResetPasswordPageContainer> | |||
| <Logo /> | |||
| <ResetPasswordTitle component="h1" variant="h5"> | |||
| {t("resetPassword.title")} | |||
| </ResetPasswordTitle> | |||
| <ResetPasswordDescription component="h1" variant="h6"> | |||
| {t("resetPassword.description")} | |||
| </ResetPasswordDescription> | |||
| <FormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <TextField | |||
| name="password" | |||
| placeholder={t("resetPassword.passwordLabel")} | |||
| margin="normal" | |||
| type={showPassword ? "text" : "password"} | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.password && formik.errors.password?.length > 0} | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconButton onClick={handleClickShowPassword}> | |||
| {showPassword ? <VisibilityOn /> : <VisibilityOff />} | |||
| </IconButton> | |||
| ), | |||
| }} | |||
| /> | |||
| <TextField | |||
| name="passwordConfirm" | |||
| placeholder={t("resetPassword.passwordConfirmLabel")} | |||
| margin="normal" | |||
| 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} | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconButton onClick={handleClickShowPasswordConfirm}> | |||
| {showPasswordConfirm ? <VisibilityOn /> : <VisibilityOff />} | |||
| </IconButton> | |||
| ), | |||
| }} | |||
| /> | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth={true} | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| disabled={ | |||
| formik.values.password.length < 8 || | |||
| formik.values.passwordConfirm.length < 8 | |||
| } | |||
| > | |||
| {t("resetPassword.buttonText")} | |||
| </PrimaryButton> | |||
| </FormContainer> | |||
| <Footer> | |||
| <FooterText> | |||
| <Trans i18nKey="forgotPassword.checkSpam" /> | |||
| </FooterText> | |||
| </Footer> | |||
| </ResetPasswordPageContainer> | |||
| ); | |||
| }; | |||
| export default ResetPasswordPage; | |||
| @@ -0,0 +1,61 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../themes"; | |||
| export const ResetPasswordPageContainer = styled(Container)` | |||
| margin-top: 200px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| `; | |||
| export const ResetPasswordTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| width: 328px; | |||
| height: 33px; | |||
| text-align: center; | |||
| flex: 1; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 24px; | |||
| line-height: 33px; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| margin-top: 36px; | |||
| `; | |||
| export const ResetPasswordDescription = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| margin-top: 9px; | |||
| width: 270px; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| display: flex; | |||
| align-items: center; | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| `; | |||
| export const FormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 216px; | |||
| `; | |||
| export const Footer = styled(Box)` | |||
| position: absolute; | |||
| bottom: 36px; | |||
| display: flex; | |||
| width: 100%; | |||
| flex-direction: row; | |||
| justify-content: center; | |||
| `; | |||
| export const FooterText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| padding-right: 6px; | |||
| text-align: center; | |||
| width: 340px; | |||
| line-height: 22px; | |||
| font-weight: 400; | |||
| padding: 0; | |||
| font-size: 16px; | |||
| `; | |||
| @@ -1,13 +1,15 @@ | |||
| export default { | |||
| accounts: { | |||
| get: 'accounts/{accountUid}', | |||
| forgotPassword: 'forgotPassword', | |||
| resetPassword: 'resetPassword', | |||
| getCurrentUserPermissions: | |||
| 'accounts/{currentAccountUid}/users/{currentUserUid}/permissions', | |||
| getAddresses: 'accounts/{accountUid}/addresses', | |||
| updateAddress: 'account/{accountUid}/addresses/{addressUid}', | |||
| deleteAddress: 'accounts/{accountUid}/addresses/{addressUid}', | |||
| getUsers: 'accounts/{accountUid}/users', | |||
| createUser: 'accounts/{accountUid}/users', | |||
| register: 'users', | |||
| updateUser: 'account/{accountUid}/users/{userUid}?actionType={actionType}', | |||
| deleteUser: 'accounts/{accountUid}/users/{userUid}', | |||
| getSettings: 'accounts/{accountUid}/settings', | |||
| @@ -17,7 +19,7 @@ export default { | |||
| }, | |||
| authentications: { | |||
| getUsernames: 'authenticate/usernames', | |||
| login: 'authenticate', | |||
| login: 'auth/token', | |||
| getUserSecurityQuestion: 'users/username/securityquestion', | |||
| confirmSecurityQuestion: 'authenticate/confirm', | |||
| confirmForgotPassword: 'users/passwords/reset_token', | |||
| @@ -108,7 +110,7 @@ export default { | |||
| getRegistrationAccounts: '/users/{userUid}/accounts', | |||
| updateUser: '/users/{userUid}?updateUserActionType={actionType}', | |||
| updateUserPassword: '/users/{userUid}/passwords', | |||
| logout: '/users/{userUid}/logout', | |||
| logout: '/auth/logout', | |||
| getUsernames: '/users/email', | |||
| createUser: | |||
| '/users?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}®istrationFlowType={registrationFlowType}', | |||
| @@ -0,0 +1,8 @@ | |||
| import { postRequest } from "."; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const forgotPasswordRequest = (payload) => | |||
| postRequest(apiEndpoints.accounts.forgotPassword, payload); | |||
| export const resetPasswordRequest = (payload) => | |||
| postRequest(apiEndpoints.accounts.resetPassword, payload); | |||
| @@ -2,11 +2,11 @@ import axios from 'axios'; | |||
| import queryString from 'qs'; | |||
| const request = axios.create({ | |||
| baseURL: process.env.REACT_APP_BASE_API_URL, | |||
| baseURL: "http://192.168.88.175:3001/", | |||
| headers: { | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| withCredentials: true, | |||
| // withCredentials: true, | |||
| paramsSerializer: (params) => | |||
| queryString.stringify(params, { arrayFormat: 'comma' }), | |||
| }); | |||
| @@ -42,6 +42,7 @@ export const replaceInUrl = (url, pathVariables = {}) => { | |||
| }; | |||
| export const addHeaderToken = (token) => { | |||
| console.log(`Bearer ${token.toString()}`) | |||
| request.defaults.headers.Authorization = `Bearer ${token}`; | |||
| }; | |||
| @@ -1,5 +1,5 @@ | |||
| import { getRequest, postRequest, replaceInUrl } from './index'; | |||
| import apiEndpoints from './apiEndpoints'; | |||
| import { getRequest, postRequest } from "./index"; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const getUsernames = (emailorusername) => | |||
| getRequest(apiEndpoints.authentications.getUsernames, { | |||
| @@ -15,12 +15,8 @@ export const updateSecurityAnswer = (payload) => | |||
| export const refreshTokenRequest = (payload) => | |||
| postRequest(apiEndpoints.authentications.refreshToken, payload); | |||
| export const logoutUserRequest = (userUid) => | |||
| postRequest( | |||
| replaceInUrl(apiEndpoints.users.logout, { | |||
| userUid, | |||
| }), | |||
| ); | |||
| export const logoutUserRequest = (payload) => | |||
| postRequest(apiEndpoints.users.logout, payload); | |||
| export const generateTokenRequest = (payload) => | |||
| postRequest(apiEndpoints.authentications.generateToken, payload); | |||
| @@ -0,0 +1,6 @@ | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| import { postRequest } from "."; | |||
| export const attemptRegister = (payload) => | |||
| postRequest(apiEndpoints.accounts.register, payload); | |||
| @@ -0,0 +1,8 @@ | |||
| import { createClearType, createSetType } from "../actionHelpers" | |||
| const FILTERS_SCOPE = "FILTERS" | |||
| export const SET_FILTERS = createSetType(FILTERS_SCOPE); | |||
| export const CLEAR_FILTERS = createClearType(FILTERS_SCOPE); | |||
| export const SET_CATEGORY = "FILTERS_SET_CATEGORY"; | |||
| export const SET_SUBCATEGORY = "FILTERS_SET_SUBCATEGORY"; | |||
| export const SET_CITIES = "FILTERS_SET_CITIES"; | |||
| @@ -0,0 +1,21 @@ | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_CITIES, SET_FILTERS, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| export const setFilters = (payload) => ({ | |||
| type: SET_FILTERS, | |||
| payload, | |||
| }) | |||
| export const clearFilters = () => ({ | |||
| type: CLEAR_FILTERS | |||
| }) | |||
| export const setCategory = (payload) => ({ | |||
| type: SET_CATEGORY, | |||
| payload | |||
| }) | |||
| export const setSubcategory = (payload) => ({ | |||
| type: SET_SUBCATEGORY, | |||
| payload | |||
| }) | |||
| export const setCities = (payload) => ({ | |||
| type: SET_CITIES, | |||
| payload | |||
| }) | |||
| @@ -0,0 +1,4 @@ | |||
| import { createFetchType } from "../actionHelpers"; | |||
| const REGISTER_USER_SCOPE = "REGISTER_USER"; | |||
| export const REGISTER_USER_FETCH = createFetchType(REGISTER_USER_SCOPE); | |||
| @@ -0,0 +1,6 @@ | |||
| import { REGISTER_USER_FETCH } from "./registerActionConstants"; | |||
| export const fetchRegisterUser = (payload) => ({ | |||
| type: REGISTER_USER_FETCH, | |||
| payload | |||
| }) | |||
| @@ -1,3 +1,5 @@ | |||
| export const SET_USER = 'SET_USER'; | |||
| export const SET_USER_ERROR = 'SET_USER_ERROR'; | |||
| export const SET_USER_ERROR = 'SET_USER_ERROR'; | |||
| export const FORGOT_PASSWORD = "FORGOT_PASSWORD"; | |||
| export const RESET_PASSWORD = "RESET_PASSWORD"; | |||
| @@ -1,4 +1,6 @@ | |||
| import { | |||
| FORGOT_PASSWORD, | |||
| RESET_PASSWORD, | |||
| SET_USER, | |||
| SET_USER_ERROR, | |||
| } from './userActionConstants'; | |||
| @@ -12,3 +14,11 @@ export const setUserError = (payload) => ({ | |||
| type: SET_USER_ERROR, | |||
| payload, | |||
| }); | |||
| export const forgotPassword = (payload) => ({ | |||
| type: FORGOT_PASSWORD, | |||
| payload | |||
| }) | |||
| export const resetPassword = (payload) => ({ | |||
| type: RESET_PASSWORD, | |||
| payload | |||
| }) | |||
| @@ -0,0 +1,68 @@ | |||
| import { | |||
| CLEAR_FILTERS, | |||
| SET_CATEGORY, | |||
| SET_CITIES, | |||
| SET_FILTERS, | |||
| SET_SUBCATEGORY, | |||
| } from "../../actions/filters/filtersActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| filters: { | |||
| category: null, | |||
| subcategory: null, | |||
| cities: [], | |||
| }, | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [SET_FILTERS]: setFilters, | |||
| [CLEAR_FILTERS]: clearFilters, | |||
| [SET_CATEGORY]: setCategory, | |||
| [SET_SUBCATEGORY]: setSubcategory, | |||
| [SET_CITIES]: setCities, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setFilters(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: payload, | |||
| }; | |||
| } | |||
| function clearFilters() { | |||
| return initialState; | |||
| } | |||
| function setCategory(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| category: payload, | |||
| }, | |||
| }; | |||
| } | |||
| function setSubcategory(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| subcategory: payload, | |||
| }, | |||
| }; | |||
| } | |||
| function setCities(state, { payload }) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| cities: payload, | |||
| }, | |||
| }; | |||
| } | |||
| @@ -6,11 +6,12 @@ import randomDataReducer from "./randomData/randomDataReducer"; | |||
| import storage from "redux-persist/lib/storage"; | |||
| import createFilter from "redux-persist-transform-filter"; | |||
| import persistReducer from "redux-persist/es/persistReducer"; | |||
| import filtersReducer from "./filters/filtersReducer"; | |||
| const loginPersistConfig = { | |||
| key: "login", | |||
| storage: storage, | |||
| transform: [createFilter("login", ["email", "token", "errorMessage"])], | |||
| transform: [createFilter("login", ["email", "token"])], | |||
| }; | |||
| const userPersistConfig = { | |||
| @@ -39,5 +40,6 @@ export default combineReducers({ | |||
| login: persistReducer(loginPersistConfig, loginReducer), | |||
| user: persistReducer(userPersistConfig, userReducer), | |||
| loading: loadingReducer, | |||
| filters: filtersReducer, | |||
| randomData: persistReducer(randomDataPersistConfig, randomDataReducer), | |||
| }); | |||
| @@ -0,0 +1,43 @@ | |||
| import { all, takeLatest, call } from "@redux-saga/core/effects"; | |||
| import { forgotPasswordRequest, resetPasswordRequest } from "../../request/forgotPasswordRequest"; | |||
| import { FORGOT_PASSWORD, RESET_PASSWORD } from "../actions/user/userActionConstants"; | |||
| function* forgotPassword({payload}) { | |||
| try { | |||
| const data = yield call(forgotPasswordRequest); | |||
| if (data) { | |||
| if (payload.handleResponseSuccess) { | |||
| yield call(payload.handleResponseSuccess); | |||
| } | |||
| } | |||
| } | |||
| catch(e) { | |||
| console.log(e); | |||
| if (payload.handleResponseError) { | |||
| yield call(payload.handleResponseError); | |||
| } | |||
| } | |||
| } | |||
| function* resetPassword({payload}) { | |||
| try { | |||
| const data = yield call(resetPasswordRequest) | |||
| if (data) { | |||
| if (payload.handleResponseSuccess) { | |||
| yield call(payload.handleResponseSuccess); | |||
| } | |||
| } | |||
| } | |||
| catch(e) { | |||
| console.log(e); | |||
| if (payload.handleResponseError) { | |||
| yield call(payload.handleResponseError); | |||
| } | |||
| } | |||
| } | |||
| export default function* forgotPasswordSaga() { | |||
| yield all([ | |||
| takeLatest(FORGOT_PASSWORD, forgotPassword), | |||
| takeLatest(RESET_PASSWORD, resetPassword) | |||
| ]) | |||
| } | |||
| @@ -1,8 +1,12 @@ | |||
| import { all } from 'redux-saga/effects'; | |||
| import forgotPasswordSaga from './forgotPasswordSaga'; | |||
| import loginSaga from './loginSaga'; | |||
| import registerSaga from './registerSaga'; | |||
| export default function* rootSaga() { | |||
| yield all([ | |||
| loginSaga(), | |||
| registerSaga(), | |||
| forgotPasswordSaga() | |||
| ]); | |||
| } | |||
| @@ -1,57 +1,60 @@ | |||
| import { all, call, put, takeLatest } from '@redux-saga/core/effects'; | |||
| import jwt from 'jsonwebtoken'; | |||
| import history from '../utils/history'; | |||
| import { all, call, put, takeLatest } from "@redux-saga/core/effects"; | |||
| import jwt from "jsonwebtoken"; | |||
| import history from "../utils/history"; | |||
| import { | |||
| AUTHENTICATE_USER, | |||
| LOGIN_USER_FETCH, | |||
| LOGOUT_USER, | |||
| REFRESH_TOKEN, | |||
| GENERATE_TOKEN, | |||
| } from '../actions/login/loginActionConstants'; | |||
| } from "../actions/login/loginActionConstants"; | |||
| import { | |||
| attemptLogin, | |||
| logoutUserRequest, | |||
| refreshTokenRequest, | |||
| generateTokenRequest, | |||
| } from '../../request/loginRequest'; | |||
| } 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'; | |||
| } 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'; | |||
| } from "../../constants/sessionStorage"; | |||
| import { | |||
| JWT_REFRESH_TOKEN, | |||
| JWT_TOKEN, | |||
| REFRESH_TOKEN_CONST, | |||
| } from '../../constants/localStorage'; | |||
| // REFRESH_TOKEN_CONST, | |||
| } from "../../constants/localStorage"; | |||
| import { | |||
| authScopeClearHelper, | |||
| authScopeStringGetHelper, | |||
| authScopeRemoveHelper, | |||
| authScopeSetHelper, | |||
| } from '../../util/helpers/authScopeHelpers'; | |||
| import { rejectErrorCodeHelper } from '../../util/helpers/rejectErrorCodeHelper'; | |||
| } from "../../util/helpers/authScopeHelpers"; | |||
| // import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| function* fetchUser({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptLogin, payload); | |||
| if (data.JwtToken) { | |||
| const user = jwt.decode(data.JwtToken); | |||
| yield call(authScopeSetHelper, JWT_TOKEN, data.JwtToken); | |||
| yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken); | |||
| yield call(authScopeSetHelper, REFRESH_TOKEN_CONST, data.RefreshToken); | |||
| yield call(addHeaderToken, data.JwtToken); | |||
| console.log(data); | |||
| if (data.tokens) { | |||
| const token = data.tokens[data.tokens.length - 1].token.toString(); | |||
| // cemu sluzi? | |||
| const user = jwt.decode(token); | |||
| console.log("userJWT: ", user); | |||
| yield call(authScopeSetHelper, JWT_TOKEN, token); | |||
| // yield call(authScopeSetHelper, JWT_REFRESH_TOKEN, data.JwtRefreshToken); | |||
| // yield call(authScopeSetHelper, REFRESH_TOKEN_CONST, data.RefreshToken); | |||
| yield call(addHeaderToken, token); | |||
| yield put(setUser(user)); | |||
| } | |||
| yield put(fetchUserSuccess(data)); | |||
| @@ -60,10 +63,14 @@ function* fetchUser({ payload }) { | |||
| } | |||
| } catch (e) { | |||
| if (e.response && e.response.data) { | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| if (payload.handleApiResponseError) { | |||
| yield call(payload.handleApiResponseError); | |||
| } | |||
| let errorMessage = "Greska!"; | |||
| if (e.response.status === 400) { | |||
| errorMessage = "Pogresan mail ili lozinka!"; | |||
| } | |||
| const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| // const errorMessage = yield call(rejectErrorCodeHelper, e); | |||
| yield put(fetchUserError(errorMessage)); | |||
| } | |||
| } | |||
| @@ -79,14 +86,14 @@ function* authenticateUser() { | |||
| return yield put( | |||
| fetchUserSuccess({ | |||
| JwtToken, | |||
| }), | |||
| }) | |||
| ); | |||
| } 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); | |||
| // yield call(authScopeRemoveHelper, REFRESH_TOKEN_CONST); | |||
| } | |||
| } | |||
| @@ -94,8 +101,9 @@ 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, user.UserUid); | |||
| yield call(logoutUserRequest, {token: JwtToken, userId: user._id}); | |||
| } | |||
| } catch (error) { | |||
| console.log(error); // eslint-disable-line | |||
| @@ -112,7 +120,7 @@ export function* refreshToken() { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| const JwtRefreshToken = yield call( | |||
| authScopeStringGetHelper, | |||
| JWT_REFRESH_TOKEN, | |||
| JWT_REFRESH_TOKEN | |||
| ); | |||
| if (JwtToken && JwtRefreshToken) { | |||
| @@ -0,0 +1,32 @@ | |||
| import { all, takeLatest, call } from "@redux-saga/core/effects"; | |||
| import { attemptRegister } from "../../request/registerRequest"; | |||
| import { REGISTER_USER_FETCH } from "../actions/register/registerActionConstants"; | |||
| function* fetchRegisterUser({payload}) { | |||
| try { | |||
| const requestData = { | |||
| name: "Djordje Mitrovic", | |||
| email: payload.mail.toString(), | |||
| password: payload.password.toString(), | |||
| company: { | |||
| name: payload.nameOfFirm.toString(), | |||
| PIB: payload.PIB.toString(), | |||
| contacts: { | |||
| telephone: payload.phoneNumber.toString(), | |||
| location: payload.location.toString(), | |||
| web: payload.website.toString() | |||
| } | |||
| } | |||
| } | |||
| console.log("payload: ", payload) | |||
| console.log(requestData); | |||
| const data = yield call(attemptRegister, requestData); | |||
| console.log(data); | |||
| } catch (e) { | |||
| console.log(e); | |||
| } | |||
| } | |||
| export default function* registerSaga() { | |||
| yield all([takeLatest(REGISTER_USER_FETCH, fetchRegisterUser)]); | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| import { createSelector } from 'reselect'; | |||
| const filtersSelector = (state) => state.filters; | |||
| export const selectFilters = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters, | |||
| ); | |||