| @@ -0,0 +1,7 @@ | |||
| <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path d="M9 4H9.5C10.0304 4 10.5391 4.21071 10.9142 4.58579C11.2893 4.96086 11.5 5.46957 11.5 6C11.5 6.53043 11.2893 7.03914 10.9142 7.41421C10.5391 7.78929 10.0304 8 9.5 8H9" stroke="#FEB005" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M1 4H9V8.5C9 9.03043 8.78929 9.53914 8.41421 9.91421C8.03914 10.2893 7.53043 10.5 7 10.5H3C2.46957 10.5 1.96086 10.2893 1.58579 9.91421C1.21071 9.53914 1 9.03043 1 8.5V4Z" stroke="#FEB005" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M3 0.5V2" stroke="#FEB005" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M5 0.5V2" stroke="#FEB005" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M7 0.5V2" stroke="#FEB005" stroke-linecap="round" stroke-linejoin="round"/> | |||
| </svg> | |||
| @@ -0,0 +1,4 @@ | |||
| <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path d="M1.5 4.5L6 1L10.5 4.5V10C10.5 10.2652 10.3946 10.5196 10.2071 10.7071C10.0196 10.8946 9.76522 11 9.5 11H2.5C2.23478 11 1.98043 10.8946 1.79289 10.7071C1.60536 10.5196 1.5 10.2652 1.5 10V4.5Z" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M4.5 11V6H7.5V11" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| </svg> | |||
| @@ -0,0 +1,6 @@ | |||
| <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path d="M8 1.5H0.5V8H8V1.5Z" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M8 4H10L11.5 5.5V8H8V4Z" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M2.75 10.5C3.44036 10.5 4 9.94036 4 9.25C4 8.55964 3.44036 8 2.75 8C2.05964 8 1.5 8.55964 1.5 9.25C1.5 9.94036 2.05964 10.5 2.75 10.5Z" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M9.25 10.5C9.94036 10.5 10.5 9.94036 10.5 9.25C10.5 8.55964 9.94036 8 9.25 8C8.55964 8 8 8.55964 8 9.25C8 9.94036 8.55964 10.5 9.25 10.5Z" stroke="#5A3984" stroke-linecap="round" stroke-linejoin="round"/> | |||
| </svg> | |||
| @@ -6,9 +6,7 @@ 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 { 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"; | |||
| @@ -25,9 +23,12 @@ import { | |||
| CreateOfferFormContainer, | |||
| RegisterAltText, | |||
| RegisterTextContainer, | |||
| FieldLabel, | |||
| } from "./CreateOffer.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import StepProgress from "../../StepProgress/StepProgress"; | |||
| import { Label } from "../../CheckBox/Label"; | |||
| import FirstPartCreateOffer from "./FirstPart/FirstPartCreateOffer"; | |||
| const CreateOffer = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| @@ -72,101 +73,15 @@ const CreateOffer = ({ history }) => { | |||
| ); | |||
| }; | |||
| 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> | |||
| <StepProgress current={1} numberOfSteps={3} /> | |||
| <FirstPartCreateOffer /> | |||
| </CreateOfferContainer> | |||
| ); | |||
| }; | |||
| @@ -1,6 +1,7 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { Label } from "../../CheckBox/Label"; | |||
| export const CreateOfferContainer = styled(Container)` | |||
| margin-top: 0px; | |||
| @@ -40,6 +41,7 @@ export const CreateOfferDescription = styled(Typography)` | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 700px; | |||
| padding-top: 20px; | |||
| `; | |||
| export const RegisterAltText = styled(Typography)` | |||
| font-family: "Poppins"; | |||
| @@ -53,4 +55,16 @@ export const RegisterTextContainer = styled(Box)` | |||
| flex-direction: row; | |||
| margin-top: 36px; | |||
| justify-content: center; | |||
| ` | |||
| export const FieldLabel = styled(Label)` | |||
| position: relative; | |||
| bottom: -14px; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| ` | |||
| @@ -0,0 +1,104 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import {useFormik} from "formik"; | |||
| import { CreateOfferFormContainer, FieldLabel } from './FirstPartCreateOffer.styled' | |||
| 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 Select from '../../../Select/Select'; | |||
| import Option from '../../../Select/Option/Option'; | |||
| const FirstPartCreateOffer = () => { | |||
| const {t} = useTranslation(); | |||
| const handleSubmit = (values) => { | |||
| console.log(values) | |||
| } | |||
| 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 ( | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <FieldLabel | |||
| leftText={"NASLOV"} | |||
| /> | |||
| <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 | |||
| /> | |||
| <FieldLabel | |||
| leftText={"OPIS PROIZVODA"} | |||
| /> | |||
| <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"} | |||
| /> | |||
| <Select defaultValue={1}> | |||
| <Option value={1}>Opcija 1</Option> | |||
| <Option value={2}>Opcija 2</Option> | |||
| <Option value={3}>Opcija 3</Option> | |||
| <Option value={4}>Opcija 4</Option> | |||
| </Select> | |||
| <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 | |||
| // } | |||
| > | |||
| NASTAVI | |||
| </PrimaryButton> | |||
| </CreateOfferFormContainer> | |||
| ) | |||
| } | |||
| FirstPartCreateOffer.propTypes = { | |||
| children: PropTypes.any, | |||
| } | |||
| export default FirstPartCreateOffer | |||
| @@ -0,0 +1,50 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { Label } from "../../../CheckBox/Label"; | |||
| 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; | |||
| padding-top: 20px; | |||
| `; | |||
| export const FieldLabel = styled(Label)` | |||
| position: relative; | |||
| bottom: -14px; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| ` | |||
| @@ -29,8 +29,8 @@ import selectedTheme from "../../../themes"; | |||
| const OfferCard = (props) => { | |||
| return ( | |||
| <OfferCardContainer sponsored={props.sponsored.toString()} halfwidth={props.halfwidth ? 1 : 0}> | |||
| <OfferImage>{props.image}</OfferImage> | |||
| <OfferCardContainer sponsored={props.offer.pinned.toString()} halfwidth={props.halfwidth ? 1 : 0}> | |||
| <OfferImage src={props.offer.images[0]}></OfferImage> | |||
| <OfferInfo> | |||
| <OfferTitle>{props.title}</OfferTitle> | |||
| <OfferAuthor> | |||
| @@ -54,7 +54,7 @@ const OfferCard = (props) => { | |||
| <DetailIcon color="black" component="span" size="16px"> | |||
| <Eye width={"12px"} height={"11px"} /> | |||
| </DetailIcon> | |||
| <DetailText>{props.numberOfViews} pregleda</DetailText> | |||
| <DetailText>{props.offer.views.viewers.length} pregleda</DetailText> | |||
| </OfferViews> | |||
| </OfferDetails> | |||
| </OfferInfo> | |||
| @@ -110,6 +110,7 @@ OfferCard.propTypes = { | |||
| numberOfViews: PropTypes.number, | |||
| halfwidth: PropTypes.bool, | |||
| sponsored: PropTypes.bool, | |||
| offer: PropTypes.any, | |||
| }; | |||
| OfferCard.defaultProps = { | |||
| halfwidth: false, | |||
| @@ -22,7 +22,7 @@ export const OfferCardContainer = styled(Container)` | |||
| height: 180px; | |||
| position: relative; | |||
| `; | |||
| export const OfferImage = styled(Box)``; | |||
| export const OfferImage = styled.img``; | |||
| export const OfferInfo = styled(Box)` | |||
| display: flex; | |||
| flex: 2; | |||
| @@ -4,8 +4,13 @@ import PropTypes from "prop-types"; | |||
| export const Label = (props) => { | |||
| return ( | |||
| <LabelContainer onClick={props.onClick} maxWidth={props.maxWidth}> | |||
| <LeftLabel>{props.leftText}</LeftLabel> | |||
| <LabelContainer | |||
| onClick={props.onClick} | |||
| maxWidth={props.maxWidth} | |||
| style={props.containerStyle} | |||
| className={props.className} | |||
| > | |||
| <LeftLabel style={props.leftTextStyle}>{props.leftText}</LeftLabel> | |||
| {props.rightText && <RightLabel>{props.rightText}</RightLabel>} | |||
| </LabelContainer> | |||
| ); | |||
| @@ -16,4 +21,8 @@ Label.propTypes = { | |||
| leftText: PropTypes.string, | |||
| rightText: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | |||
| maxWidth: PropTypes.string, | |||
| leftTextStyle: PropTypes.any, | |||
| rightTextStyle: PropTypes.any, | |||
| containerStyle: PropTypes.any, | |||
| className: PropTypes.any, | |||
| }; | |||
| @@ -1,8 +1,10 @@ | |||
| import React from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { OffersContainer } from "./Offers.styled"; | |||
| import OfferCard from "../../Cards/OfferCard/OfferCard"; | |||
| import MockupdataOffers from "../MockupdataOffers"; | |||
| import { fetchOffers } from "../../../store/actions/offers/offersActions"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectOffers } from "../../../store/selectors/offersSelectors"; | |||
| // import { fetchOffers } from "../../../store/actions/offers/offersActions"; | |||
| // import { useDispatch, useSelector } from "react-redux"; | |||
| // import { selectOffers } from "../../../store/selectors/offersSelectors"; | |||
| @@ -11,17 +13,17 @@ const Offers = (props) => { | |||
| // Market place nije zavrsen | |||
| // Koriste se Mockup podaci | |||
| // const dispatch = useDispatch(); | |||
| // const offers = useSelector(selectOffers); | |||
| const dispatch = useDispatch(); | |||
| const offers = useSelector(selectOffers); | |||
| // useEffect(() => { | |||
| // dispatch(fetchOffers()); | |||
| // }, []) | |||
| useEffect(() => { | |||
| dispatch(fetchOffers()); | |||
| }, []) | |||
| return ( | |||
| <OffersContainer> | |||
| {MockupdataOffers.map((item) => { | |||
| return <OfferCard {...item} key={item.id} halfwidth={props.isGrid} />; | |||
| {offers.map((item) => { | |||
| return <OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />; | |||
| })} | |||
| </OffersContainer> | |||
| ); | |||
| @@ -0,0 +1,27 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { OptionIcon, OptionStyled } from './Option.styled' | |||
| const Option = props => { | |||
| console.log(props) | |||
| return ( | |||
| <OptionStyled {...props}> | |||
| {props.startIcon ? ( | |||
| <OptionIcon color={props.color}> | |||
| {props.startIcon} | |||
| </OptionIcon> | |||
| ) :(<> | |||
| </>)} | |||
| {props.children} | |||
| </OptionStyled> | |||
| ) | |||
| } | |||
| Option.propTypes = { | |||
| children: PropTypes.node, | |||
| color: PropTypes.any, | |||
| startIcon: PropTypes.any, | |||
| } | |||
| export default Option | |||
| @@ -0,0 +1,13 @@ | |||
| import { Box, MenuItem } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const OptionStyled = styled(MenuItem)` | |||
| background-color: ${props => props.selected ? selectedTheme.primaryPurple : "white"} !important; | |||
| color: ${props => props.selected ? "white" : selectedTheme.selectOptionTextColor}; | |||
| &:hover { | |||
| background-color: ${selectedTheme.primaryPurple} !important; | |||
| } | |||
| ` | |||
| export const OptionIcon = styled(Box)` | |||
| ` | |||
| @@ -0,0 +1,35 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { SelectIcon, SelectStyled } from "./Select.styled"; | |||
| 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} | |||
| fullWidth={props.fullwidth} | |||
| IconComponent={(iconProps) => ( | |||
| <SelectIcon {...iconProps}> | |||
| <Down /> | |||
| </SelectIcon> | |||
| )} | |||
| > | |||
| {props.children} | |||
| </SelectStyled> | |||
| ); | |||
| }; | |||
| Select.propTypes = { | |||
| children: PropTypes.node, | |||
| width: PropTypes.string, | |||
| fullwidth: PropTypes.bool, | |||
| defaultValue: PropTypes.number, | |||
| }; | |||
| Select.defaultProps = { | |||
| fullwidth: true, | |||
| }; | |||
| export default Select; | |||
| @@ -0,0 +1,12 @@ | |||
| import { Box, Select } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const SelectStyled = styled(Select)` | |||
| width: ${props => props.width}; | |||
| height: ${props => props.height}; | |||
| ` | |||
| export const SelectIcon = styled(Box)` | |||
| position: relative; | |||
| top: 0px; | |||
| left: -15px; | |||
| ` | |||
| @@ -42,6 +42,7 @@ export const TextField = (props) => { | |||
| sx={props.style} | |||
| label={props.showAnimation ? props.placeholder : ""} | |||
| italicplaceholder={props.italicPlaceholder && isFieldEmpty} | |||
| ref={textInputRef} | |||
| focused={props.focused} | |||
| @@ -20,6 +20,7 @@ export const TextFieldStyled = styled(TextField)` | |||
| padding: 0; | |||
| height: ${(props) => props.height}; | |||
| box-sizing: border-box; | |||
| overflow-y: hidden; | |||
| & div { | |||
| padding-left: 2px; | |||
| @@ -28,16 +29,21 @@ export const TextFieldStyled = styled(TextField)` | |||
| ` | |||
| padding: 10px 16px; | |||
| max-height: ${props.height}; | |||
| overflow-y: auto; | |||
| & fieldset { | |||
| border: 1px solid rgba(0, 0, 0, 0.23); | |||
| border-radius: 4px; | |||
| position: relative; | |||
| height: 100%; | |||
| & textarea { | |||
| height: 100% !important; | |||
| width: 100% ; | |||
| overflow: auto; | |||
| font-size: 16px; | |||
| } | |||
| `} | |||
| } | |||
| & div input { | |||
| height: ${(props) => props.height}; | |||
| box-sizing: border-box; | |||
| font-size: ${(props) => | |||
| props.textsize ? props.textsize : "16px"} !important; | |||
| font-family: ${(props) => (props.font ? props.font : "")}; | |||
| @@ -7,6 +7,12 @@ export const ForgotPasswordPageContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @media (max-height: 900px) { | |||
| margin-top: 160px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 120px; | |||
| } | |||
| `; | |||
| export const ForgotPasswordTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| MailSentContainer, | |||
| @@ -21,17 +21,22 @@ import { useDispatch } from "react-redux"; | |||
| import { forgotPassword } from "../../../store/actions/user/userActions"; | |||
| const MailSent = () => { | |||
| const [mail, setEmail] = useState(""); | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| const location = useLocation(); | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| setEmail(location.state.email); | |||
| }, []) | |||
| const navigateLogin = () => { | |||
| history.replace(LOGIN_PAGE); | |||
| }; | |||
| const handleResend = () => { | |||
| dispatch(forgotPassword(location.state.email)) | |||
| dispatch(forgotPassword({email: mail})) | |||
| } | |||
| return ( | |||
| @@ -7,6 +7,12 @@ export const MailSentContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @media (max-height: 900px) { | |||
| margin-top: 200px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 150px; | |||
| } | |||
| `; | |||
| export const Title = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -35,16 +35,14 @@ const ForgotPasswordPage = () => { | |||
| pathname: FORGOT_PASSWORD_MAIL_SENT, | |||
| state: {email: formik.values.email} | |||
| }); | |||
| } | |||
| const handleResponseError = () => { | |||
| setEmailNotFoundStatus(true); | |||
| } | |||
| // const handleResponseError = () => { | |||
| // setEmailNotFoundStatus(true); | |||
| // console.log("greska!"); | |||
| // } | |||
| const handleSubmit = (values) => { | |||
| // validate email | |||
| dispatch(forgotPassword({email: values.email, handleResponseSuccess, handleResponseError: handleResponseSuccess})); | |||
| dispatch(forgotPassword({email: values.email, handleResponseSuccess, handleResponseError})); | |||
| }; | |||
| const formik = useFormik({ | |||
| @@ -42,7 +42,6 @@ const HomePage = () => { | |||
| (item) => item.string === queryObject.subcategory.toString() | |||
| ).id; | |||
| } | |||
| console.log("iz useeffect: ", { category, subcategory, cities }); | |||
| dispatch(setFilters({ category, subcategory, cities })); | |||
| }, [history.location.search]); | |||
| const handleCl = () => { | |||
| @@ -7,6 +7,12 @@ export const LoginPageContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @media (max-height: 900px) { | |||
| margin-top: 110px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 70px; | |||
| } | |||
| `; | |||
| export const LoginTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -1,6 +1,7 @@ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ErrorMessage, | |||
| FormContainer, | |||
| RegisterDescription, | |||
| } from "./FirstPartOfRegistration.styled"; | |||
| @@ -20,7 +21,6 @@ const FirstPartOfRegistration = (props) => { | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| console.log(props.error); | |||
| if (props.error.length > 0) { | |||
| setEmailTakenStatus(true); | |||
| } | |||
| @@ -90,6 +90,8 @@ const FirstPartOfRegistration = (props) => { | |||
| }} | |||
| /> | |||
| {props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)} | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| @@ -113,6 +115,7 @@ FirstPartOfRegistration.propTypes = { | |||
| children: PropTypes.node, | |||
| handleSubmit: PropTypes.func, | |||
| error: PropTypes.string, | |||
| errorMessage: PropTypes.string, | |||
| }; | |||
| export default FirstPartOfRegistration; | |||
| @@ -19,4 +19,12 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-top: 31px; | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: "Open Sans"; | |||
| position: relative; | |||
| top: -7px; | |||
| font-size: 14px; | |||
| ` | |||
| @@ -29,6 +29,8 @@ const Register = () => { | |||
| 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 handleResponseSuccess = () => { | |||
| @@ -36,17 +38,22 @@ const Register = () => { | |||
| }; | |||
| const handleResponseError = (error) => { | |||
| console.log("handleResponse: ", error); | |||
| if (error.type === "mail") { | |||
| const { mail } = informations; | |||
| setInformations({}); | |||
| setCurrentStep(1); | |||
| setMailError(mail); | |||
| if (error.error.response.data.toString() === 'User with email already exists') { | |||
| setMailErrorMessage("Vec postoji korisnik sa zadatim mail-om!"); | |||
| } else { | |||
| setMailErrorMessage("Nevalidan format mail-a!") | |||
| } | |||
| } else { | |||
| const { mail, password, PIB } = informations; | |||
| setInformations({ mail, password }); | |||
| setCurrentStep(2); | |||
| setPIBError(PIB.toString()); | |||
| setPIBErrorMessage("PIB broj je zauzet!"); | |||
| } | |||
| }; | |||
| @@ -65,7 +72,7 @@ const Register = () => { | |||
| setInformations({ ...informations, ...values }); | |||
| }; | |||
| return ( | |||
| <RegisterPageContainer> | |||
| <RegisterPageContainer currentStep={currentStep}> | |||
| <Logo /> | |||
| <RegisterTitle component="h1" variant="h5"> | |||
| @@ -84,12 +91,14 @@ const Register = () => { | |||
| <FirstPartOfRegistration | |||
| handleSubmit={handleSubmit} | |||
| error={mailError} | |||
| errorMessage={mailErrorMessage} | |||
| /> | |||
| )} | |||
| {currentStep === 2 && ( | |||
| <SecondPartOfRegistration | |||
| handleSubmit={handleSubmit} | |||
| error={PIBError} | |||
| errorMessage={PIBErrorMessage} | |||
| /> | |||
| )} | |||
| {currentStep === 3 && ( | |||
| @@ -9,19 +9,33 @@ export const RegisterPageContainer = styled(Container)` | |||
| align-items: center; | |||
| width: 335px; | |||
| padding: 0; | |||
| flex: 1; | |||
| position: relative; | |||
| @media (max-height: 900px) { | |||
| margin-top: 60px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 30px; | |||
| flex: none; | |||
| height: 95vh; | |||
| ${props => props.currentStep === 3 && `height: 105vh`}; | |||
| ${props => props.currentStep === 2 && `height: 100vh`}; | |||
| } | |||
| `; | |||
| export const RegisterTitle = 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: 34px; | |||
| @media (max-height: 800px) { | |||
| margin-top: 26px; | |||
| } | |||
| `; | |||
| export const RegisterDescription = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -36,6 +50,10 @@ export const RegisterDescription = styled(Typography)` | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| @media (max-height: 800px) { | |||
| margin-bottom: 14px; | |||
| margin-top: 6px; | |||
| } | |||
| `; | |||
| export const FormContainer = styled(Box)` | |||
| width: 335px; | |||
| @@ -52,6 +70,9 @@ export const LoginTextContainer = styled(Box)` | |||
| flex-direction: row; | |||
| margin-top: 36px; | |||
| justify-content: center; | |||
| @media (max-height: 800px) { | |||
| margin-top: 26px; | |||
| } | |||
| ` | |||
| export const ProgressContainer = styled(Container)` | |||
| width: 100%; | |||
| @@ -64,6 +85,9 @@ export const Footer = styled(Box)` | |||
| width: 100%; | |||
| flex-direction: row; | |||
| justify-content: center; | |||
| @media (max-height: 800px) { | |||
| bottom: 10px; | |||
| } | |||
| ` | |||
| export const FooterText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -75,12 +99,3 @@ export const FooterText = styled(Typography)` | |||
| padding: 0; | |||
| font-size: 12px; | |||
| ` | |||
| export const RegisterDescriptionPart = styled(RegisterDescription)` | |||
| font-size: 12px; | |||
| width: 100%; | |||
| text-align: left; | |||
| line-height: 16px; | |||
| margin-top: 31px; | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| ` | |||
| @@ -1,6 +1,7 @@ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ErrorMessage, | |||
| FormContainer, | |||
| RegisterDescription, | |||
| } from "./SecondPartOfRegistration.styled"; | |||
| @@ -64,6 +65,8 @@ const SecondPartOfRegistration = (props) => { | |||
| fullWidth={true} | |||
| /> | |||
| {props.error && (<ErrorMessage>{props.errorMessage}</ErrorMessage>)} | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| @@ -87,6 +90,7 @@ SecondPartOfRegistration.propTypes = { | |||
| children: PropTypes.node, | |||
| handleSubmit: PropTypes.func, | |||
| error: PropTypes.string, | |||
| errorMessage: PropTypes.string, | |||
| }; | |||
| export default SecondPartOfRegistration; | |||
| @@ -21,3 +21,10 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: "Open Sans"; | |||
| position: relative; | |||
| top: -7px; | |||
| font-size: 14px; | |||
| ` | |||
| @@ -19,4 +19,7 @@ export const RegisterDescription = styled(Typography)` | |||
| margin-top: 31px; | |||
| margin-bottom: 2px; | |||
| letter-spacing: 0.02em; | |||
| @media (max-height: 800px) { | |||
| margin-top: 21px; | |||
| } | |||
| `; | |||
| @@ -7,6 +7,12 @@ export const ResetPasswordPageContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @media (max-height: 900px) { | |||
| margin-top: 140px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 70px; | |||
| } | |||
| `; | |||
| export const ResetPasswordTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| @@ -1,8 +1,10 @@ | |||
| import { postRequest } from "."; | |||
| import { postRequest, getRequest } from "."; | |||
| import apiEndpoints from "./apiEndpoints"; | |||
| export const forgotPasswordRequest = (payload) => | |||
| postRequest(apiEndpoints.accounts.forgotPassword, payload); | |||
| export const forgotPasswordRequest = (payload) => { | |||
| const encodedString = encodeURIComponent(payload); | |||
| return getRequest(`${apiEndpoints.accounts.forgotPassword}` + '/' + `${encodedString}`); | |||
| } | |||
| export const resetPasswordRequest = (payload) => | |||
| postRequest(apiEndpoints.accounts.resetPassword, payload); | |||
| @@ -7,4 +7,5 @@ export const OFFERS_SUCCESS = createSuccessType(OFFERS_SCOPE); | |||
| export const OFFERS_ERROR = createErrorType(OFFERS_SCOPE); | |||
| export const OFFERS_CLEAR = createClearType(OFFERS_SCOPE); | |||
| export const OFFERS_SET = "OFFERS_SET"; | |||
| export const OFFERS_SET = "OFFERS_SET"; | |||
| export const OFFERS_ADD = "OFFERS_ADD"; | |||
| @@ -1,4 +1,4 @@ | |||
| import { 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 } from "./offersActionConstants"; | |||
| export const fetchOffers = (payload) => ({ | |||
| type: OFFERS_FETCH, | |||
| @@ -18,4 +18,8 @@ export const clearOffers = () => ({ | |||
| export const setOffers = (payload) => ({ | |||
| type: OFFERS_SET, | |||
| payload, | |||
| }) | |||
| export const addOffers = (payload) => ({ | |||
| type: OFFERS_ADD, | |||
| payload | |||
| }) | |||
| @@ -1,4 +1,5 @@ | |||
| import { | |||
| OFFERS_ADD, | |||
| OFFERS_CLEAR, | |||
| OFFERS_ERROR, | |||
| OFFERS_SET, | |||
| @@ -15,6 +16,7 @@ export default createReducer( | |||
| [OFFERS_ERROR]: fetchOffersError, | |||
| [OFFERS_CLEAR]: clearOffers, | |||
| [OFFERS_SET]: setOffers, | |||
| [OFFERS_ADD]: addOffers, | |||
| }, | |||
| initialState | |||
| ); | |||
| @@ -32,3 +34,9 @@ function setOffers(state, action) { | |||
| offers: action.payload, | |||
| }; | |||
| } | |||
| function addOffers(state, action) { | |||
| return { | |||
| ...state, | |||
| offers: [...state.offers, ...action.payload] | |||
| } | |||
| } | |||
| @@ -4,7 +4,8 @@ import { FORGOT_PASSWORD, RESET_PASSWORD } from "../actions/user/userActionConst | |||
| function* forgotPassword({payload}) { | |||
| try { | |||
| const data = yield call(forgotPasswordRequest); | |||
| console.log(payload) | |||
| const data = yield call(forgotPasswordRequest, payload.email); | |||
| if (data) { | |||
| if (payload.handleResponseSuccess) { | |||
| yield call(payload.handleResponseSuccess); | |||
| @@ -12,7 +13,6 @@ function* forgotPassword({payload}) { | |||
| } | |||
| } | |||
| catch(e) { | |||
| console.log(e); | |||
| if (payload.handleResponseError) { | |||
| yield call(payload.handleResponseError); | |||
| } | |||
| @@ -28,7 +28,6 @@ function* resetPassword({payload}) { | |||
| } | |||
| } | |||
| catch(e) { | |||
| console.log(e); | |||
| if (payload.handleResponseError) { | |||
| yield call(payload.handleResponseError); | |||
| } | |||
| @@ -43,13 +43,11 @@ import { | |||
| function* fetchUser({ payload }) { | |||
| try { | |||
| const { data } = yield call(attemptLogin, payload); | |||
| 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); | |||
| @@ -1,11 +1,13 @@ | |||
| import { all, takeLatest, call } from "@redux-saga/core/effects"; | |||
| import { all, takeLatest, call, put } from "@redux-saga/core/effects"; | |||
| import { attemptFetchOffers } from "../../request/offersRequest"; | |||
| import { OFFERS_FETCH } from "../actions/offers/offersActionConstants"; | |||
| import { setOffers } from "../actions/offers/offersActions"; | |||
| function* fetchOffers() { | |||
| try { | |||
| const data = yield call(attemptFetchOffers); | |||
| console.log(data); | |||
| console.log(data.data.items) | |||
| yield put(setOffers(data.data.items)) | |||
| } catch (e) { | |||
| console.log(e); | |||
| } | |||
| @@ -2,46 +2,47 @@ 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}) { | |||
| function* fetchRegisterUser({ payload }) { | |||
| try { | |||
| console.log("payload: ", payload) | |||
| const requestData = { | |||
| email: payload.values.mail.toString(), | |||
| password: payload.values.password.toString(), | |||
| roles: [ | |||
| { | |||
| role: "User" | |||
| } | |||
| ], | |||
| company: { | |||
| name: payload.values.nameOfFirm.toString(), | |||
| PIB: payload.values.PIB.toString(), | |||
| contacts: { | |||
| telephone: payload.values.phoneNumber.toString(), | |||
| location: payload.values.location.toString(), | |||
| web: payload.values.website.toString() | |||
| } | |||
| } | |||
| } | |||
| console.log(requestData); | |||
| const data = yield call(attemptRegister, requestData); | |||
| console.log(data); | |||
| email: payload.values.mail.toString(), | |||
| password: payload.values.password.toString(), | |||
| roles: [ | |||
| { | |||
| role: "User", | |||
| }, | |||
| ], | |||
| company: { | |||
| name: payload.values.nameOfFirm.toString(), | |||
| PIB: payload.values.PIB.toString(), | |||
| contacts: { | |||
| telephone: payload.values.phoneNumber.toString(), | |||
| location: payload.values.location.toString(), | |||
| web: payload.values.website.toString(), | |||
| }, | |||
| }, | |||
| }; | |||
| yield call(attemptRegister, requestData); | |||
| if (payload.handleResponseSuccess) { | |||
| yield call(payload.handleResponseSuccess); | |||
| } | |||
| } catch (e) { | |||
| console.log(e.response.data); | |||
| let type; | |||
| if (e.response?.data?.toString() === 'User with email already exists') { | |||
| type = 'mail' | |||
| } else if (e.response?.data?.toString() === 'Company with PIB already exists') { | |||
| type = "PIB" | |||
| if ( | |||
| e.response?.data?.toString() === "User with email already exists" || | |||
| e.response?.data?.toString() === '"email" must be a valid email' | |||
| ) { | |||
| type = "mail"; | |||
| } else if ( | |||
| e.response?.data?.toString() === "Company with PIB already exists" | |||
| ) { | |||
| type = "PIB"; | |||
| } | |||
| const error = { | |||
| error: e, | |||
| type | |||
| } | |||
| type, | |||
| }; | |||
| if (payload.handleResponseError) { | |||
| yield call(payload.handleResponseError, error); | |||
| } | |||
| @@ -11,6 +11,7 @@ export const primaryThemeColors = { | |||
| borderSponsoredColor: "#E5D0FF", | |||
| backgroundSponsoredColor: "#F5EDFF", | |||
| offerBackgroundColor: "#F5F5F5", | |||
| selectOptionTextColor: "#1D1D1D", | |||
| primaryDarkText: "#505050", | |||
| iconStrokeColor: "#8C8C8C" | |||
| } | |||