| @@ -41,20 +41,21 @@ import ChatPage from "./pages/Chat/Chat"; | |||
| import MyOffers from "./pages/MyOffers/MyOffers"; | |||
| // import PricesPage from "./pages/Prices/PricesPage"; | |||
| import AboutPage from "./pages/About/AboutPage"; | |||
| import AuthRoute from "./components/Router/AuthRoute"; | |||
| // import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage"; | |||
| const AppRoutes = () => { | |||
| return ( | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||
| <Route exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <AuthRoute exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| <Route path={REGISTER_PAGE} component={Register} /> | |||
| <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} /> | |||
| <AuthRoute path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| <AuthRoute path={REGISTER_PAGE} component={Register} /> | |||
| <AuthRoute path={FORGOT_PASSWORD_MAIL_SENT} component={MailSent} /> | |||
| <AuthRoute path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> | |||
| <AuthRoute path={RESET_PASSWORD_PAGE} component={ResetPasswordPage} /> | |||
| <Route path={CREATE_OFFER_PAGE} component={CreateOffer} /> | |||
| <Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} /> | |||
| <Route path={PROFILE_PAGE} component={ProfilePage} /> | |||
| @@ -1,7 +1,7 @@ | |||
| import IconButton from "../../IconButton/IconButton"; | |||
| import { ReactComponent as DownArrow } from "../../../assets/images/svg/arrow-down.svg"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../IconButton/IconButton"; | |||
| export const ArrowIcon = styled(DownArrow)` | |||
| ${(props) => | |||
| @@ -11,6 +11,9 @@ export const ArrowIcon = styled(DownArrow)` | |||
| `} | |||
| width: 18px; | |||
| height: 18px; | |||
| position: relative; | |||
| top: 1px; | |||
| left: 1px; | |||
| & path { | |||
| ${(props) => | |||
| props.disabled && | |||
| @@ -2,8 +2,8 @@ import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { ReactComponent as Phone } from "../../../../assets/images/svg/phone.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { IconButton } from "../../../Buttons/IconButton/IconButton"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import IconButton from "../../../IconButton/IconButton"; | |||
| import PopoverComponent from "../../../Popovers/PopoverComponent"; | |||
| @@ -27,7 +27,6 @@ const FilterCard = (props) => { | |||
| skeleton={props.skeleton} | |||
| > | |||
| <SkeletonFilterCard | |||
| animationStage={props.animationStage} | |||
| skeleton={props.skeleton} | |||
| /> | |||
| {/* Header title for my offers */} | |||
| @@ -75,7 +74,6 @@ FilterCard.propTypes = { | |||
| closeResponsive: PropTypes.func, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| filtersOpened: PropTypes.bool, | |||
| toggleFilters: PropTypes.func, | |||
| }; | |||
| @@ -8,21 +8,20 @@ import { | |||
| SkeletonChooserContainer, | |||
| } from "./SkeletonChooserHeader.styled"; | |||
| const SkeletonChooserHeader = (props) => { | |||
| const SkeletonChooserHeader = () => { | |||
| return ( | |||
| <SkeletonChooserContainer> | |||
| <LeftContainer> | |||
| <CircleOne animationStage={props.animationStage}/> | |||
| <Line animationStage={props.animationStage}/> | |||
| <CircleOne/> | |||
| <Line/> | |||
| </LeftContainer> | |||
| <CircleSecond animationStage={props.animationStage}/> | |||
| <CircleSecond/> | |||
| </SkeletonChooserContainer> | |||
| ); | |||
| }; | |||
| SkeletonChooserHeader.propTypes = { | |||
| children: PropTypes.node, | |||
| animationStage: PropTypes.any, | |||
| }; | |||
| export default SkeletonChooserHeader; | |||
| @@ -4,8 +4,8 @@ import { SkeletonChooserTitleContainer, SkeletonChooserTitleLine } from './Skele | |||
| const SkeletonChooserTitle = (props) => { | |||
| return ( | |||
| <SkeletonChooserTitleContainer center={props.center} animationStage={props.animationStage} > | |||
| <SkeletonChooserTitleLine center={props.center} animationStage={props.animationStage}/> | |||
| <SkeletonChooserTitleContainer center={props.center} > | |||
| <SkeletonChooserTitleLine center={props.center} /> | |||
| </SkeletonChooserTitleContainer> | |||
| ) | |||
| } | |||
| @@ -13,7 +13,6 @@ const SkeletonChooserTitle = (props) => { | |||
| SkeletonChooserTitle.propTypes = { | |||
| children: PropTypes.any, | |||
| center: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| } | |||
| export default SkeletonChooserTitle | |||
| @@ -13,26 +13,25 @@ import SkeletonSection from "./SkeletonSection/SkeletonSection"; | |||
| const SkeletonFilterCard = (props) => { | |||
| return ( | |||
| <SkeletonFilterCardContainer animationStage={props.animationStage} skeleton={props.skeleton}> | |||
| <SkeletonFilterCardContainer skeleton={props.skeleton}> | |||
| <SkeletonHeader> | |||
| <SkeletonHeaderLineOne animationStage={props.animationStage} /> | |||
| <SkeletonHeaderLineSecond animationStage={props.animationStage} /> | |||
| <SkeletonHeaderLineOne /> | |||
| <SkeletonHeaderLineSecond /> | |||
| </SkeletonHeader> | |||
| <SkeletonChooserHeader animationStage={props.animationStage}/> | |||
| <SkeletonChooserTitle animationStage={props.animationStage} /> | |||
| <SkeletonSection numberOfOptions={14} animationStage={props.animationStage} /> | |||
| <SkeletonChooserHeader animationStage={props.animationStage} /> | |||
| <SkeletonChooserHeader animationStage={props.animationStage} /> | |||
| <SkeletonChooserTitle animationStage={props.animationStage} /> | |||
| <SkeletonSection numberOfOptions={3} animationStage={props.animationStage} /> | |||
| <SkeletonChooserTitle center animationStage={props.animationStage} /> | |||
| <SkeletonChooserHeader/> | |||
| <SkeletonChooserTitle /> | |||
| <SkeletonSection numberOfOptions={14} /> | |||
| <SkeletonChooserHeader /> | |||
| <SkeletonChooserHeader /> | |||
| <SkeletonChooserTitle /> | |||
| <SkeletonSection numberOfOptions={3} /> | |||
| <SkeletonChooserTitle center /> | |||
| </SkeletonFilterCardContainer> | |||
| ); | |||
| }; | |||
| SkeletonFilterCard.propTypes = { | |||
| children: PropTypes.any, | |||
| animationStage: PropTypes.number, | |||
| skeleton: PropTypes.bool, | |||
| }; | |||
| @@ -10,7 +10,7 @@ const SkeletonSection = (props) => { | |||
| return ( | |||
| <SkeletonSectionContainer> | |||
| {arrayForMapping.map((item, index) => ( | |||
| <SkeletonSectionOption key={index} animationStage={props.animationStage} /> | |||
| <SkeletonSectionOption key={index} /> | |||
| ))} | |||
| </SkeletonSectionContainer> | |||
| ); | |||
| @@ -19,7 +19,6 @@ const SkeletonSection = (props) => { | |||
| SkeletonSection.propTypes = { | |||
| children: PropTypes.node, | |||
| numberOfOptions: PropTypes.number, | |||
| animationStage: PropTypes.number, | |||
| }; | |||
| export default SkeletonSection; | |||
| @@ -2,21 +2,20 @@ import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { Circle, EndLine, Line, OptionLeftContainer, SkeletonSectionOptionContainer } from './SkeletonSectionOption.styled' | |||
| const SkeletonSectionOption = (props) => { | |||
| const SkeletonSectionOption = () => { | |||
| return ( | |||
| <SkeletonSectionOptionContainer> | |||
| <OptionLeftContainer> | |||
| <Circle animationStage={props.animationStage} /> | |||
| <Line animationStage={props.animationStage} /> | |||
| <Circle/> | |||
| <Line/> | |||
| </OptionLeftContainer> | |||
| <EndLine animationStage={props.animationStage} /> | |||
| <EndLine/> | |||
| </SkeletonSectionOptionContainer> | |||
| ) | |||
| } | |||
| SkeletonSectionOption.propTypes = { | |||
| children: PropTypes.any, | |||
| animationStage: PropTypes.number, | |||
| } | |||
| export default SkeletonSectionOption | |||
| @@ -57,7 +57,6 @@ const SkeletonOfferCard = (props) => { | |||
| SkeletonOfferCard.propTypes = { | |||
| children: PropTypes.node, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| }; | |||
| export default SkeletonOfferCard; | |||
| @@ -0,0 +1,25 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { InputField, InputFieldLabel } from "../EditProfile.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const AppLinkField = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <> | |||
| <InputFieldLabel leftText={t("editProfile.applink").toUpperCase()} /> | |||
| <InputField | |||
| name="firmApplink" | |||
| values={props.formik.values.firmApplink} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| AppLinkField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default AppLinkField; | |||
| @@ -0,0 +1,167 @@ | |||
| import React, { useState, useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import BackdropComponent from "../../../MUI/BackdropComponent"; | |||
| import { | |||
| EditProfileContainer, | |||
| ProfileImageContainer, | |||
| BackButton, | |||
| CloseButton, | |||
| SaveButton, | |||
| ProfileHeader, | |||
| BasicInfo, | |||
| DetailsInfo, | |||
| ButtonsContainer, | |||
| ProfileImagePicker, | |||
| } from "./EditProfile.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { useFormik } from "formik"; | |||
| import { ReactComponent as ArrowBack } from "../../../../assets/images/svg/arrow-back.svg"; | |||
| import { ReactComponent as CloseIcon } from "../../../../assets/images/svg/close-modal.svg"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| editMineProfile, | |||
| fetchMineProfile, | |||
| } from "../../../../store/actions/profile/profileActions"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../../store/selectors/loginSelectors"; | |||
| import editProfileValidation from "../../../../validations/editProfileValidation"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| import { useMemo } from "react"; | |||
| import editProfileInitialValues from "../../../../initialValues/editProfileInitialValues"; | |||
| import FirmNameField from "./FirmNameField/FirmNameField"; | |||
| import PIBField from "./PIBField/PIBField"; | |||
| import LocationField from "./LocationField/LocationField"; | |||
| import WebsiteField from "./WebsiteField/WebsiteField"; | |||
| import AppLinkField from "./AppLinkField/AppLinkField"; | |||
| import PhoneField from "./PhoneField/PhoneField"; | |||
| import FormikErrorMessage from "./FormikErrorMessage/FormikErrorMessage"; | |||
| const EditProfile = (props) => { | |||
| const [profileImage, setProfileImage] = useState(props.profile.image); | |||
| const [showBasic, setShowBasic] = useState(true); | |||
| const [showDetails, setShowDetails] = useState(true); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const { isMobile } = useIsMobile(); | |||
| const userId = useSelector(selectUserId); | |||
| useEffect(() => { | |||
| setShowDetails(!isMobile); | |||
| }, [isMobile]); | |||
| const handleApiResponseSuccess = () => { | |||
| dispatch(fetchMineProfile(userId)); | |||
| props.reFetchProfile(); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| dispatch(editMineProfile({ ...values, handleApiResponseSuccess })); | |||
| props.closeModalHandler(); | |||
| }; | |||
| const initialValues = useMemo( | |||
| () => editProfileInitialValues(props?.profile), | |||
| [props?.profile] | |||
| ); | |||
| const formik = useFormik({ | |||
| initialValues, | |||
| validationSchema: editProfileValidation, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| const closeEditModalHandler = () => { | |||
| props.closeModalHandler(); | |||
| }; | |||
| const showDetailsHandler = () => { | |||
| setShowDetails(!showDetails); | |||
| setShowBasic(!showBasic); | |||
| }; | |||
| const setImage = (image) => { | |||
| setProfileImage(image); | |||
| }; | |||
| return ( | |||
| <> | |||
| <BackdropComponent | |||
| handleClose={closeEditModalHandler} | |||
| isLoading | |||
| position="fixed" | |||
| /> | |||
| <EditProfileContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {!showBasic && ( | |||
| <BackButton onClick={showDetailsHandler}> | |||
| <ArrowBack /> | |||
| </BackButton> | |||
| )} | |||
| <ProfileImageContainer> | |||
| <ProfileImagePicker | |||
| image={profileImage} | |||
| setImage={setImage} | |||
| ></ProfileImagePicker> | |||
| <ProfileHeader>{props.profile.company.name}</ProfileHeader> | |||
| </ProfileImageContainer> | |||
| <CloseButton onClick={closeEditModalHandler}> | |||
| <CloseIcon /> | |||
| </CloseButton> | |||
| {showBasic && ( | |||
| <BasicInfo> | |||
| <FirmNameField formik={formik} /> | |||
| <PIBField formik={formik} /> | |||
| <LocationField formik={formik} /> | |||
| </BasicInfo> | |||
| )} | |||
| {showDetails && ( | |||
| <DetailsInfo> | |||
| <WebsiteField formik={formik} /> | |||
| <AppLinkField formik={formik} /> | |||
| <PhoneField formik={formik} /> | |||
| </DetailsInfo> | |||
| )} | |||
| <FormikErrorMessage formik={formik} /> | |||
| <ButtonsContainer> | |||
| {isMobile && ( | |||
| <> | |||
| <SaveButton | |||
| height="44px" | |||
| width="155px" | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor={selectedTheme.colors.primaryPurple} | |||
| onClick={showDetailsHandler} | |||
| > | |||
| {showDetails | |||
| ? t("editProfile.showBasic") | |||
| : t("editProfile.showDetails")} | |||
| </SaveButton> | |||
| </> | |||
| )} | |||
| <SaveButton | |||
| type="submit" | |||
| variant="contained" | |||
| height={isMobile ? "44px" : "48px"} | |||
| width={isMobile ? "155px" : "335px"} | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor="white" | |||
| > | |||
| {t("common.save")} | |||
| </SaveButton> | |||
| </ButtonsContainer> | |||
| </EditProfileContainer> | |||
| </> | |||
| ); | |||
| }; | |||
| EditProfile.propTypes = { | |||
| children: PropTypes.node, | |||
| profile: PropTypes.any, | |||
| closeModalHandler: PropTypes.func, | |||
| setImage: PropTypes.func, | |||
| reFetchProfile: PropTypes.func, | |||
| }; | |||
| export default EditProfile; | |||
| @@ -1,9 +1,9 @@ | |||
| import styled from "styled-components"; | |||
| import { Box, TextField, Typography } from "@mui/material"; | |||
| import ImagePicker from "../../ImagePicker/ImagePicker"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Label } from "../../CheckBox/Label"; | |||
| import selectedTheme from "../../../themes"; | |||
| import ImagePicker from "../../../ImagePicker/ImagePicker"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Label } from "../../../CheckBox/Label"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const EditProfileContainer = styled(Box)` | |||
| background-color: #fff; | |||
| @@ -120,29 +120,6 @@ export const InputField = styled(TextField)` | |||
| } | |||
| `; | |||
| export const InputFieldLabelLocation = styled(Label)` | |||
| position: relative; | |||
| bottom: -8px; | |||
| margin: 10px 0; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| color: #808080; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| bottom: -12px; | |||
| margin: 5px 0 17px 0; | |||
| & label { | |||
| font-size: 9px; | |||
| margin-top: 0; | |||
| } | |||
| } | |||
| `; | |||
| export const SaveButton = styled(PrimaryButton)` | |||
| font-size: 12px; | |||
| letter-spacing: 1.5px; | |||
| @@ -158,14 +135,6 @@ export const ButtonsContainer = styled(Box)` | |||
| } | |||
| `; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| position: relative; | |||
| top: 20px; | |||
| font-size: 14px; | |||
| `; | |||
| export const BasicInfo = styled(Box)``; | |||
| export const DetailsInfo = styled(Box)``; | |||
| @@ -0,0 +1,27 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { InputField, InputFieldLabel } from "../EditProfile.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const FirmNameField = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <> | |||
| <InputFieldLabel leftText={t("common.labelFirm").toUpperCase()} /> | |||
| <InputField | |||
| name="firmName" | |||
| value={props.formik.values.firmName} | |||
| onChange={props.formik.handleChange} | |||
| error={props.formik.touched.firmName && props.formik.errors.firmName} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| FirmNameField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default FirmNameField; | |||
| @@ -0,0 +1,28 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ErrorMessage } from "./FormikErrorMessage.styled"; | |||
| const FormikErrorMessage = (props) => { | |||
| return ( | |||
| <> | |||
| {props.formik.errors.firmName && props.formik.touched.firmName ? ( | |||
| <ErrorMessage>{props.formik.errors.firmName}</ErrorMessage> | |||
| ) : props.formik.errors.firmPIB && props.formik.touched.firmPIB ? ( | |||
| <ErrorMessage>{props.formik.errors.firmPIB}</ErrorMessage> | |||
| ) : props.formik.errors.firmLocation && | |||
| props.formik.touched.firmLocation ? ( | |||
| <ErrorMessage>{props.formik.errors.firmLocation}</ErrorMessage> | |||
| ) : props.formik.errors.firmPhone && props.formik.touched.firmPhone ? ( | |||
| <ErrorMessage>{props.formik.errors.firmPhone}</ErrorMessage> | |||
| ) : ( | |||
| <></> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| FormikErrorMessage.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default FormikErrorMessage; | |||
| @@ -0,0 +1,11 @@ | |||
| import { Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| export const ErrorMessage = styled(Typography)` | |||
| color: red; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| position: relative; | |||
| top: 20px; | |||
| font-size: 14px; | |||
| `; | |||
| @@ -0,0 +1,42 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import AutoSuggestTextField from "../../../../TextFields/AutoSuggestTextField/AutoSuggestTextField"; | |||
| import { InputFieldLabelLocation } from "./LocationField.styled"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLocations } from "../../../../../store/selectors/locationsSelectors"; | |||
| import { useEffect } from "react"; | |||
| import { fetchLocations } from "../../../../../store/actions/locations/locationsActions"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const LocationField = (props) => { | |||
| const { t } = useTranslation(); | |||
| const locations = useSelector(selectLocations); | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| if (locations?.length === 0) { | |||
| dispatch(fetchLocations()); | |||
| } | |||
| }, [locations]); | |||
| return ( | |||
| <> | |||
| <InputFieldLabelLocation | |||
| leftText={t("common.labelLocation").toUpperCase()} | |||
| /> | |||
| <AutoSuggestTextField | |||
| editLocation | |||
| data={locations.map((item) => ({ name: item.city }))} | |||
| value={props.formik.values.firmLocation} | |||
| onChange={(event, { newValue }) => | |||
| props.formik.setFieldValue("firmLocation", newValue) | |||
| } | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| LocationField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default LocationField; | |||
| @@ -0,0 +1,25 @@ | |||
| import styled from "styled-components"; | |||
| import { Label } from "../../../../CheckBox/Label"; | |||
| export const InputFieldLabelLocation = styled(Label)` | |||
| position: relative; | |||
| bottom: -8px; | |||
| margin: 10px 0; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| color: #808080; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| bottom: -12px; | |||
| margin: 5px 0 17px 0; | |||
| & label { | |||
| font-size: 9px; | |||
| margin-top: 0; | |||
| } | |||
| } | |||
| `; | |||
| @@ -0,0 +1,29 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { InputField, InputFieldLabel } from "../EditProfile.styled"; | |||
| const PIBField = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <> | |||
| <InputFieldLabel leftText={t("common.labelPIB")} /> | |||
| <InputField | |||
| name="firmPIB" | |||
| type="number" | |||
| value={props.formik.values.firmPIB} | |||
| onChange={props.formik.handleChange} | |||
| error={props.formik.touched.firmPIB && props.formik.errors.firmPIB} | |||
| margin="normal" | |||
| fullWidth | |||
| disabled | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| PIBField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default PIBField; | |||
| @@ -0,0 +1,39 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { InputField, InputFieldLabel } from "../EditProfile.styled"; | |||
| const PhoneField = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <> | |||
| <InputFieldLabel leftText={t("editProfile.phoneNumber").toUpperCase()} /> | |||
| <InputField | |||
| type="number" | |||
| name="firmPhone" | |||
| value={props.formik.values.firmPhone} | |||
| onChange={(event) => { | |||
| props.formik.setFieldValue("firmPhone", event.target.value); | |||
| }} | |||
| error={props.formik.touched.firmPhone && props.formik.errors.firmPhone} | |||
| margin="normal" | |||
| fullWidth | |||
| onInput={(e) => { | |||
| e.target.value = | |||
| e.target.value[0] === "0" && e.target.value.length > 1 | |||
| ? "0" + | |||
| String( | |||
| Math.max(0, parseInt(e.target.value)).toString().slice(0, 14) | |||
| ) | |||
| : Math.max(0, parseInt(e.target.value)).toString().slice(0, 14); | |||
| }} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| PhoneField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default PhoneField; | |||
| @@ -0,0 +1,29 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { InputField, InputFieldLabel } from "../EditProfile.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const WebsiteField = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <> | |||
| <InputFieldLabel | |||
| leftText={t("editProfile.website").toUpperCase()} | |||
| labelWebsite | |||
| /> | |||
| <InputField | |||
| name="firmWebsite" | |||
| value={props.formik.values.firmWebsite} | |||
| onChange={props.formik.handleChange} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| WebsiteField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default WebsiteField; | |||
| @@ -13,23 +13,23 @@ import { | |||
| } from "./ProfileCard.styled"; | |||
| import PersonOutlineIcon from "@mui/icons-material/PersonOutline"; | |||
| import { useRouteMatch } from "react-router-dom"; | |||
| import { fetchProfile } from "../../store/actions/profile/profileActions"; | |||
| import { fetchProfile } from "../../../store/actions/profile/profileActions"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useEffect } from "react"; | |||
| import { selectProfile } from "../../store/selectors/profileSelectors"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { selectProfile } from "../../../store/selectors/profileSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import { useState } from "react"; | |||
| import { fetchProfileOffers } from "../../store/actions/offers/offersActions"; | |||
| import { fetchProfileOffers } from "../../../store/actions/offers/offersActions"; | |||
| import EditProfile from "./EditProfile/EditProfile"; | |||
| import ProfileMainInfo from "./ProfileMainInfo/ProfileMainInfo"; | |||
| import ProfileContact from "./ProfileContact/ProfileContact"; | |||
| import ProfileStats from "./ProfileStats/ProfileStats"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { PROFILE_SCOPE } from "../../store/actions/profile/profileActionConstants"; | |||
| import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors"; | |||
| import { PROFILE_SCOPE } from "../../../store/actions/profile/profileActionConstants"; | |||
| import SkeletonProfileCard from "./SkeletonProfileCard/SkeletonProfileCard"; | |||
| import { useMemo } from "react"; | |||
| import companyData from "../../notFoundData/companyData"; | |||
| import companyData from "../../../notFoundData/companyData"; | |||
| const ProfileCard = () => { | |||
| const [isMyProfile, setIsMyProfile] = useState(false); | |||
| @@ -51,7 +51,6 @@ const ProfileCard = () => { | |||
| return companyData; | |||
| }, [profileFromRedux]); | |||
| console.log("profile", profile); | |||
| useEffect(() => { | |||
| if (idProfile?.length > 0) { | |||
| @@ -1,10 +1,10 @@ | |||
| import styled from "styled-components"; | |||
| import { Card, Typography, Grid, Box } from "@mui/material"; | |||
| import selectedTheme from "../../themes"; | |||
| import { ReactComponent as Edit } from "../../assets/images/svg/edit.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { ReactComponent as Edit } from "../../../assets/images/svg/edit.svg"; | |||
| // import { ReactComponent as Pocket } from "../../assets/images/svg/pocket.svg"; | |||
| // import { ReactComponent as Globe } from "../../assets/images/svg/globe.svg"; | |||
| import { ReactComponent as Mail } from "../../assets/images/svg/mail.svg"; | |||
| import { ReactComponent as Mail } from "../../../assets/images/svg/mail.svg"; | |||
| // import { ReactComponent as Location } from "../../assets/images/svg/location.svg"; | |||
| // import { PRIMARY_PURPLE_COLOR, PRIMARY_YELLOW_COLOR } from '../../constants/stylesConstants'; | |||
| @@ -1,9 +1,9 @@ | |||
| import styled from "styled-components"; | |||
| import { Grid, Typography } from "@mui/material"; | |||
| import { ReactComponent as Location } from "../../../assets/images/svg/location.svg"; | |||
| import { ReactComponent as Mail } from "../../../assets/images/svg/mail.svg"; | |||
| import { ReactComponent as Globe } from "../../../assets/images/svg/globe.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { ReactComponent as Location } from "../../../../assets/images/svg/location.svg"; | |||
| import { ReactComponent as Mail } from "../../../../assets/images/svg/mail.svg"; | |||
| import { ReactComponent as Globe } from "../../../../assets/images/svg/globe.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const ProfileContactContainer = styled(Grid)` | |||
| padding-top: 2rem; | |||
| @@ -11,8 +11,8 @@ import { | |||
| ProfilePIB, | |||
| } from "./ProfileMainInfo.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| const ProfileMainInfo = (props) => { | |||
| const { t } = useTranslation(); | |||
| @@ -1,7 +1,7 @@ | |||
| import styled from "styled-components"; | |||
| import { Grid, Typography } from "@mui/material"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { ReactComponent as Pocket } from "../../../assets/images/svg/pocket.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { ReactComponent as Pocket } from "../../../../assets/images/svg/pocket.svg"; | |||
| export const ProfileMainInfoContainer = styled(Grid)` | |||
| display: flex; | |||
| @@ -1,6 +1,6 @@ | |||
| import styled from "styled-components"; | |||
| import { Grid, Typography } from "@mui/material"; | |||
| import selectedTheme from "../../../themes"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const ProfileStatsContainer = styled(Grid)` | |||
| display: flex; | |||
| @@ -1,6 +1,6 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled, { keyframes } from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import selectedTheme from "../../../../themes"; | |||
| const skeletonAnimation = keyframes` | |||
| 0% { | |||
| @@ -14,7 +14,7 @@ import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg"; | |||
| import { IconStyled } from "../Icon/Icon.styled"; | |||
| import { Grid } from "@mui/material"; | |||
| import MailOutlineIcon from "@mui/icons-material/MailOutline"; | |||
| import { HeaderTitle } from "../ProfileCard/ProfileCard.styled"; | |||
| import { HeaderTitle } from "../Cards/ProfileCard/ProfileCard.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLatestChats } from "../../store/selectors/chatSelectors"; | |||
| @@ -1,10 +1,10 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton"; | |||
| import IconButton from "../IconButton/IconButton"; | |||
| import { ReactComponent as Close } from "../../assets/images/svg/close-modal.svg"; | |||
| import { ReactComponent as ArrowBack } from "../../assets/images/svg/arrow-back.svg"; | |||
| import selectedTheme from "../../themes"; | |||
| import { IconButton } from "../Buttons/IconButton/IconButton"; | |||
| export const CreateReviewContainer = styled(Box)` | |||
| background-color: #fff; | |||
| @@ -1,50 +1,25 @@ | |||
| import React, { useState, useEffect, useRef } from "react"; | |||
| import { | |||
| AuthButtonsContainer, | |||
| // EndIcon, | |||
| // FilterContainer, | |||
| // FilterIcon, | |||
| HeaderContainer, | |||
| LoginButton, | |||
| LogoContainer, | |||
| RegisterButton, | |||
| // SearchIcon, | |||
| // SearchInput, | |||
| // SearchInputMobile, | |||
| ToolsButtonsContainer, | |||
| ToolsContainer, | |||
| } from "./Header.styled"; | |||
| import PropTypes from "prop-types"; | |||
| import { AppBar, Toolbar, useMediaQuery } from "@mui/material"; | |||
| import { useTheme } from "@mui/system"; | |||
| import PopoverComponent from "../Popovers/PopoverComponent"; | |||
| import { MyPosts } from "../Popovers/MyPosts/MyPosts"; | |||
| import { MyMessages } from "../Popovers/MyMessages/MyMessages"; | |||
| import { MyProfile } from "../Popovers/MyProfile/MyProfile"; | |||
| import { ReactComponent as LogoHorizontal } from "../../assets/images/svg/logo-horizontal.svg"; | |||
| import selectedTheme from "../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { selectProfileName } from "../../store/selectors/profileSelectors"; | |||
| import { useHistory, useRouteMatch } from "react-router-dom"; | |||
| import { | |||
| ABOUT_PAGE, | |||
| BASE_PAGE, | |||
| FORGOT_PASSWORD_MAIL_SENT, | |||
| FORGOT_PASSWORD_PAGE, | |||
| HOME_PAGE, | |||
| LOGIN_PAGE, | |||
| REGISTER_PAGE, | |||
| REGISTER_SUCCESSFUL_PAGE, | |||
| RESET_PASSWORD_PAGE, | |||
| } from "../../constants/pages"; | |||
| import { ABOUT_PAGE, BASE_PAGE, HOME_PAGE } from "../../constants/pages"; | |||
| import { fetchMineProfile } from "../../store/actions/profile/profileActions"; | |||
| import CreateOffer from "../Cards/CreateOfferCard/CreateOffer"; | |||
| import useSearch from "../../hooks/useOffers/useSearch"; | |||
| import { routeMatches } from "../../util/helpers/routeHelpers"; | |||
| import { isAuthRoute, routeMatches } from "../../util/helpers/routeHelpers"; | |||
| import AboutHeader from "./AboutHeader/AboutHeader"; | |||
| // import { useCallback } from "react"; | |||
| import SearchInput from "./SearchInput/SearchInput"; | |||
| import DrawerContainer from "./DrawerContainer/DrawerContainer"; | |||
| import OpenDrawerButton from "./OpenDrawerButton/OpenDrawerButton"; | |||
| @@ -52,11 +27,11 @@ import AddOfferButton from "./AddOfferButton/AddOfferButton"; | |||
| import MySwapsButton from "./MySwapsButton/MySwapsButton"; | |||
| import MyMessagesButton from "./MyMessagesButton/MyMessagesButton"; | |||
| import UserButton from "./UserButton/UserButton"; | |||
| import LoginButton from "./LoginButton/LoginButton"; | |||
| import RegisterButton from "./RegisterButton/RegisterButton"; | |||
| const Header = () => { | |||
| const setShowSearchBar = useState(true)[1]; | |||
| const [showCreateOfferModal, setShowCreateOfferModal] = useState(false); | |||
| const { t } = useTranslation(); | |||
| const theme = useTheme(); | |||
| const searchRef = useRef(null); | |||
| const matches = useMediaQuery(theme.breakpoints.down("md")); | |||
| @@ -65,85 +40,37 @@ const Header = () => { | |||
| const dispatch = useDispatch(); | |||
| const name = useSelector(selectProfileName); | |||
| const history = useHistory(); | |||
| const routeMatch = useRouteMatch(); | |||
| const drawerRef = useRef(null); | |||
| const [shouldShow, setShouldShow] = useState(true); | |||
| const routeMatch = useRouteMatch(); | |||
| // Dont show header on auth routes(login, register, etc.) | |||
| useEffect(() => { | |||
| if (isAuthRoute()) setShouldShow(false); | |||
| else setShouldShow(true); | |||
| }, [routeMatch]); | |||
| // Fetch mine profile on loading home page | |||
| useEffect(() => { | |||
| if (user && user?.length > 0) { | |||
| dispatch(fetchMineProfile()); | |||
| } | |||
| }, []); | |||
| useEffect(() => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| return () => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| }; | |||
| }, []); | |||
| // Sets value into search input based on query string | |||
| useEffect(() => { | |||
| if (searchRef.current) { | |||
| searchRef.current.value = search.searchString ?? ""; | |||
| } | |||
| }, [search.searchString]); | |||
| useEffect(() => { | |||
| if (history.location.pathname !== "/home") { | |||
| setShowSearchBar(false); | |||
| } else { | |||
| setShowSearchBar(true); | |||
| } | |||
| }, [history.location.pathname]); | |||
| const closeCreateOfferModal = () => { | |||
| setShowCreateOfferModal(false); | |||
| }; | |||
| }, [search.searchString, searchRef.current]); | |||
| // Removes scroll when modal is open | |||
| if (showCreateOfferModal) { | |||
| document.body.style.overflow = "hidden"; | |||
| } else { | |||
| document.body.style.overflow = "auto"; | |||
| } | |||
| const [postsPopoverOpen, setPostsPopoverOpen] = useState(false); | |||
| const [postsAnchorEl, setPostsAnchorEl] = useState(null); | |||
| const [msgPopoverOpen, setMsgPopoverOpen] = useState(false); | |||
| const [msgAnchorEl, setMsgAnchorEl] = useState(null); | |||
| const [userPopoverOpen, setUserPopoverOpen] = useState(false); | |||
| const [userAnchorEl, setUserAnchorEl] = useState(null); | |||
| const [shouldShow, setShouldShow] = useState(true); | |||
| useEffect(() => { | |||
| let shouldShowHeader = true; | |||
| if ( | |||
| routeMatches(LOGIN_PAGE) || | |||
| routeMatches(REGISTER_PAGE) || | |||
| routeMatches(REGISTER_SUCCESSFUL_PAGE) || | |||
| routeMatches(FORGOT_PASSWORD_PAGE) || | |||
| routeMatches(FORGOT_PASSWORD_MAIL_SENT) || | |||
| routeMatches(RESET_PASSWORD_PAGE) | |||
| ) { | |||
| shouldShowHeader = false; | |||
| } | |||
| setShouldShow(shouldShowHeader); | |||
| }, [location.pathname, user, routeMatch]); | |||
| useEffect(() => { | |||
| setUserPopoverOpen(false); | |||
| setMsgPopoverOpen(false); | |||
| setPostsPopoverOpen(false); | |||
| }, [location.pathname]); | |||
| const handleNavigateLogin = () => { | |||
| setShouldShow(false); | |||
| history.push(LOGIN_PAGE); | |||
| }; | |||
| const handleNavigateRegister = () => { | |||
| setShouldShow(false); | |||
| history.push(REGISTER_PAGE); | |||
| }; | |||
| // Handling search when user is on marketplace and when he is not | |||
| const handleSearch = (value) => { | |||
| if (!routeMatches(HOME_PAGE) && !routeMatches(BASE_PAGE)) { | |||
| const newQueryString = new URLSearchParams({ search: value }); | |||
| @@ -155,10 +82,8 @@ const Header = () => { | |||
| search.searchOffers(value); | |||
| } | |||
| }; | |||
| // const toggleFilters = () => { | |||
| // setOpenFilters((prevState) => !prevState); | |||
| // }; | |||
| // When user clicks logo, he sends specific state so filters can be removed | |||
| const handleLogoClick = () => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| @@ -167,34 +92,12 @@ const Header = () => { | |||
| }, | |||
| }); | |||
| }; | |||
| const handleAddOfferClick = () => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| setShowCreateOfferModal(true); | |||
| }; | |||
| const openPostsPopover = (event) => { | |||
| setPostsPopoverOpen(true); | |||
| setPostsAnchorEl(event.currentTarget); | |||
| }; | |||
| const openMesgPopover = (event) => { | |||
| setMsgPopoverOpen(true); | |||
| setMsgAnchorEl(event.currentTarget); | |||
| }; | |||
| const openUserPopover = (event) => { | |||
| setUserPopoverOpen(true); | |||
| setUserAnchorEl(event.currentTarget); | |||
| }; | |||
| const closePostsPopover = () => { | |||
| setPostsPopoverOpen(false); | |||
| setPostsAnchorEl(null); | |||
| }; | |||
| const closeMsgPopover = () => { | |||
| setMsgPopoverOpen(false); | |||
| setMsgAnchorEl(null); | |||
| }; | |||
| const closeUserPopover = () => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| const closeCreateOfferModal = () => { | |||
| setShowCreateOfferModal(false); | |||
| }; | |||
| return ( | |||
| @@ -210,11 +113,6 @@ const Header = () => { | |||
| <LogoHorizontal /> | |||
| </LogoContainer> | |||
| <DrawerContainer | |||
| ref={drawerRef} | |||
| showCreateOfferModal={setShowCreateOfferModal} | |||
| /> | |||
| <SearchInput ref={searchRef} handleSearch={handleSearch} /> | |||
| {routeMatches(ABOUT_PAGE) && <AboutHeader />} | |||
| @@ -232,11 +130,11 @@ const Header = () => { | |||
| {!routeMatches(ABOUT_PAGE) && ( | |||
| <> | |||
| <AddOfferButton onClick={handleAddOfferClick} /> | |||
| <MySwapsButton onClick={openPostsPopover} /> | |||
| <MyMessagesButton onClick={openMesgPopover} /> | |||
| <MySwapsButton /> | |||
| <MyMessagesButton /> | |||
| </> | |||
| )} | |||
| <UserButton onClick={openUserPopover} name={name}/> | |||
| <UserButton name={name} /> | |||
| </React.Fragment> | |||
| )} | |||
| </ToolsButtonsContainer> | |||
| @@ -248,87 +146,20 @@ const Header = () => { | |||
| /> | |||
| ) : ( | |||
| <React.Fragment> | |||
| <LoginButton | |||
| type="submit" | |||
| variant="contained" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor="white" | |||
| onClick={handleNavigateLogin} | |||
| > | |||
| {t("login.headerTitle")} | |||
| </LoginButton> | |||
| <RegisterButton | |||
| type="submit" | |||
| variant="contained" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.colors.primaryYellow} | |||
| textcolor={selectedTheme.colors.yellowButtonTextColor} | |||
| onClick={handleNavigateRegister} | |||
| > | |||
| {t("register.headerTitle")} | |||
| </RegisterButton> | |||
| <LoginButton /> | |||
| <RegisterButton /> | |||
| </React.Fragment> | |||
| )} | |||
| </AuthButtonsContainer> | |||
| )} | |||
| </ToolsContainer> | |||
| </Toolbar> | |||
| {user && ( | |||
| <React.Fragment> | |||
| <PopoverComponent | |||
| anchorEl={postsAnchorEl} | |||
| open={postsPopoverOpen} | |||
| onClose={() => { | |||
| setPostsPopoverOpen(false); | |||
| setPostsAnchorEl(null); | |||
| }} | |||
| content={<MyPosts closePopover={closePostsPopover} />} | |||
| /> | |||
| <PopoverComponent | |||
| anchorEl={msgAnchorEl} | |||
| open={msgPopoverOpen} | |||
| onClose={() => { | |||
| setMsgPopoverOpen(false); | |||
| setMsgAnchorEl(null); | |||
| }} | |||
| content={<MyMessages closePopover={closeMsgPopover} />} | |||
| /> | |||
| <PopoverComponent | |||
| anchorEl={userAnchorEl} | |||
| open={userPopoverOpen} | |||
| onClose={() => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| }} | |||
| content={<MyProfile closePopover={closeUserPopover} />} | |||
| /> | |||
| </React.Fragment> | |||
| )} | |||
| </AppBar> | |||
| {/* <SearchInputMobile | |||
| fullWidth | |||
| shouldShow={showSearchBar} | |||
| ref={searchMobileRef} | |||
| placeholder={t("header.searchOffers")} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <EndIcon size="36px"> | |||
| <SearchIcon | |||
| onClick={() => handleSearch(searchMobileRef.current.value)} | |||
| /> | |||
| </EndIcon> | |||
| ), | |||
| }} | |||
| italicPlaceholder | |||
| onFocus={handleFocusSearch} | |||
| onBlur={handleBlurSearch} | |||
| /> */} | |||
| {/* <FilterCard | |||
| responsive={true} | |||
| responsiveOpen={openFilters} | |||
| closeResponsive={toggleFilters} | |||
| /> */} | |||
| <DrawerContainer | |||
| ref={drawerRef} | |||
| showCreateOfferModal={setShowCreateOfferModal} | |||
| /> | |||
| {showCreateOfferModal && ( | |||
| <CreateOffer closeCreateOfferModal={closeCreateOfferModal} /> | |||
| )} | |||
| @@ -1,6 +1,5 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton"; | |||
| export const DrawerContainer = styled(Box)` | |||
| @@ -57,20 +56,7 @@ export const ToolsContainer = styled(Box)` | |||
| ${(props) => props.mobile && `width: auto;`} | |||
| } | |||
| `; | |||
| export const RegisterButton = styled(PrimaryButton)` | |||
| height: 49px; | |||
| width: 180px; | |||
| font-weight: 600; | |||
| @media (max-width: 550px) { | |||
| margin-bottom: 20px; | |||
| } | |||
| `; | |||
| export const LoginButton = styled(PrimaryButton)` | |||
| height: 49px; | |||
| width: 180px; | |||
| font-weight: 600; | |||
| margin-right: 10px; | |||
| `; | |||
| export const AuthButtonsContainer = styled(Box)` | |||
| display: flex; | |||
| justify-content: flex-start; | |||
| @@ -0,0 +1,32 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { LoginButtonContainer } from "./LoginButton.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { LOGIN_PAGE } from "../../../constants/pages"; | |||
| import history from "../../../store/utils/history"; | |||
| const LoginButton = () => { | |||
| const { t } = useTranslation(); | |||
| const handleNavigateLogin = () => { | |||
| history.push(LOGIN_PAGE); | |||
| }; | |||
| return ( | |||
| <LoginButtonContainer | |||
| type="submit" | |||
| variant="contained" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor="white" | |||
| onClick={handleNavigateLogin} | |||
| > | |||
| {t("login.headerTitle")} | |||
| </LoginButtonContainer> | |||
| ); | |||
| }; | |||
| LoginButton.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default LoginButton; | |||
| @@ -0,0 +1,9 @@ | |||
| import styled from "styled-components"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| export const LoginButtonContainer = styled(PrimaryButton)` | |||
| height: 49px; | |||
| width: 180px; | |||
| font-weight: 600; | |||
| margin-right: 10px; | |||
| `; | |||
| @@ -4,20 +4,51 @@ import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import MailIcon from "@mui/icons-material/EmailOutlined"; | |||
| import { Badge } from "@mui/material"; | |||
| import { useState } from "react"; | |||
| import PopoverComponent from "../../Popovers/PopoverComponent"; | |||
| import { MyMessages } from "../../Popovers/MyMessages/MyMessages"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { useEffect } from "react"; | |||
| const MyMessagesButton = (props) => { | |||
| const MyMessagesButton = () => { | |||
| const location = useLocation(); | |||
| const [msgPopoverOpen, setMsgPopoverOpen] = useState(false); | |||
| const [msgAnchorEl, setMsgAnchorEl] = useState(null); | |||
| useEffect(() => { | |||
| setMsgPopoverOpen(false); | |||
| }, [location.pathname]); | |||
| const openMsgPopover = (event) => { | |||
| setMsgPopoverOpen(true); | |||
| setMsgAnchorEl(event.currentTarget); | |||
| }; | |||
| const closeMsgPopover = () => { | |||
| setMsgPopoverOpen(false); | |||
| setMsgAnchorEl(null); | |||
| }; | |||
| return ( | |||
| <IconButton | |||
| onClick={props.onClick} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| }} | |||
| > | |||
| <Badge color="primary"> | |||
| <MailIcon /> | |||
| </Badge> | |||
| </IconButton> | |||
| <> | |||
| <IconButton | |||
| onClick={openMsgPopover} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| }} | |||
| > | |||
| <Badge color="primary"> | |||
| <MailIcon /> | |||
| </Badge> | |||
| </IconButton> | |||
| <PopoverComponent | |||
| anchorEl={msgAnchorEl} | |||
| open={msgPopoverOpen} | |||
| onClose={() => { | |||
| setMsgPopoverOpen(false); | |||
| setMsgAnchorEl(null); | |||
| }} | |||
| content={<MyMessages closePopover={closeMsgPopover} />} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -3,18 +3,50 @@ import PropTypes from "prop-types"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { SwapsIcon } from "./MySwapsButton.styled"; | |||
| import PopoverComponent from "../../Popovers/PopoverComponent"; | |||
| import { MyPosts } from "../../Popovers/MyPosts/MyPosts"; | |||
| import { useState } from "react"; | |||
| import { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| const MySwapsButton = (props) => { | |||
| const MySwapsButton = () => { | |||
| const location = useLocation(); | |||
| const [postsPopoverOpen, setPostsPopoverOpen] = useState(false); | |||
| const [postsAnchorEl, setPostsAnchorEl] = useState(null); | |||
| useEffect(() => { | |||
| setPostsPopoverOpen(false); | |||
| }, [location.pathname]); | |||
| const openPostsPopover = (event) => { | |||
| setPostsPopoverOpen(true); | |||
| setPostsAnchorEl(event.currentTarget); | |||
| }; | |||
| const closePostsPopover = () => { | |||
| setPostsPopoverOpen(false); | |||
| setPostsAnchorEl(null); | |||
| }; | |||
| return ( | |||
| <IconButton | |||
| onClick={props.onClick} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| }} | |||
| > | |||
| <SwapsIcon /> | |||
| </IconButton> | |||
| <> | |||
| <IconButton | |||
| onClick={openPostsPopover} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| }} | |||
| > | |||
| <SwapsIcon /> | |||
| </IconButton> | |||
| <PopoverComponent | |||
| anchorEl={postsAnchorEl} | |||
| open={postsPopoverOpen} | |||
| onClose={() => { | |||
| setPostsPopoverOpen(false); | |||
| setPostsAnchorEl(null); | |||
| }} | |||
| content={<MyPosts closePopover={closePostsPopover} />} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,32 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { RegisterButtonContainer } from "./RegisterButton.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { REGISTER_PAGE } from "../../../constants/pages"; | |||
| import history from "../../../store/utils/history"; | |||
| const RegisterButton = () => { | |||
| const { t } = useTranslation(); | |||
| const handleNavigateRegister = () => { | |||
| history.push(REGISTER_PAGE); | |||
| }; | |||
| return ( | |||
| <RegisterButtonContainer | |||
| type="submit" | |||
| variant="contained" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.colors.primaryYellow} | |||
| textcolor={selectedTheme.colors.yellowButtonTextColor} | |||
| onClick={handleNavigateRegister} | |||
| > | |||
| {t("register.headerTitle")} | |||
| </RegisterButtonContainer> | |||
| ); | |||
| }; | |||
| RegisterButton.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default RegisterButton; | |||
| @@ -0,0 +1,11 @@ | |||
| import styled from "styled-components"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| export const RegisterButtonContainer = styled(PrimaryButton)` | |||
| height: 49px; | |||
| width: 180px; | |||
| font-weight: 600; | |||
| @media (max-width: 550px) { | |||
| margin-bottom: 20px; | |||
| } | |||
| `; | |||
| @@ -4,26 +4,58 @@ import selectedTheme from "../../../themes"; | |||
| import { AccountCircle } from "@mui/icons-material"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { UserButtonContainer, UserName } from "./UserButton.styled"; | |||
| import PopoverComponent from "../../Popovers/PopoverComponent"; | |||
| import { MyProfile } from "../../Popovers/MyProfile/MyProfile"; | |||
| import { useState } from "react"; | |||
| import { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| const UserButton = (props) => { | |||
| const location = useLocation(); | |||
| const [userPopoverOpen, setUserPopoverOpen] = useState(false); | |||
| const [userAnchorEl, setUserAnchorEl] = useState(null); | |||
| useEffect(() => { | |||
| setUserPopoverOpen(false); | |||
| }, [location.pathname]); | |||
| const openUserPopover = (event) => { | |||
| setUserPopoverOpen(true); | |||
| setUserAnchorEl(event.currentTarget); | |||
| }; | |||
| const closeUserPopover = () => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| }; | |||
| return ( | |||
| <UserButtonContainer onClick={props.onClick}> | |||
| <UserName>{props.name}</UserName> | |||
| <IconButton | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| <> | |||
| <UserButtonContainer onClick={openUserPopover}> | |||
| <UserName>{props.name}</UserName> | |||
| <IconButton | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| }} | |||
| > | |||
| <AccountCircle /> | |||
| </IconButton> | |||
| </UserButtonContainer> | |||
| <PopoverComponent | |||
| anchorEl={userAnchorEl} | |||
| open={userPopoverOpen} | |||
| onClose={() => { | |||
| setUserPopoverOpen(false); | |||
| setUserAnchorEl(null); | |||
| }} | |||
| > | |||
| <AccountCircle /> | |||
| </IconButton> | |||
| </UserButtonContainer> | |||
| content={<MyProfile closePopover={closeUserPopover} />} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| UserButton.propTypes = { | |||
| onClick: PropTypes.func, | |||
| name: PropTypes.string, | |||
| onClick: PropTypes.func, | |||
| name: PropTypes.string, | |||
| }; | |||
| export default UserButton; | |||
| @@ -1,32 +0,0 @@ | |||
| import React, { useRef } from 'react'; | |||
| import PropType from 'prop-types'; | |||
| const IconButton = ({ children, onClick, className }) => { | |||
| const buttonRef = useRef(null); | |||
| function handleClick(event) { | |||
| buttonRef.current.blur(); | |||
| if (typeof onClick === 'function') { | |||
| onClick(event); | |||
| } | |||
| } | |||
| return ( | |||
| <button | |||
| type="button" | |||
| ref={buttonRef} | |||
| onClick={handleClick} | |||
| className={`c-icon-button ${className && className}`} | |||
| > | |||
| {children} | |||
| </button> | |||
| ); | |||
| }; | |||
| IconButton.propTypes = { | |||
| children: PropType.node, | |||
| onClick: PropType.func, | |||
| className: PropType.string, | |||
| }; | |||
| export default IconButton; | |||
| @@ -30,7 +30,7 @@ const ImagePicker = (props) => { | |||
| } | |||
| }, [props.image]); | |||
| let listener = useCallback( | |||
| const listener = useCallback( | |||
| (event) => { | |||
| if (imageRef.current) { | |||
| if (imageRef.current.contains(event.target)) { | |||
| @@ -1,12 +1,12 @@ | |||
| import React, { forwardRef, useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import IconButton from "../../../IconButton/IconButton"; | |||
| import { ReactComponent as VisibilityOn } from "../../../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../../../assets/images/svg/eye.svg"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectLoginError } from "../../../../store/selectors/loginSelectors"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { IconButton } from "../../../Buttons/IconButton/IconButton"; | |||
| const PasswordField = forwardRef((props, ref) => { | |||
| const formik = props.formik; | |||
| @@ -14,22 +14,11 @@ const PasswordField = forwardRef((props, ref) => { | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
| const error = useSelector(selectLoginError); | |||
| const {t} = useTranslation(); | |||
| // useEffect(() => { | |||
| // console.log("error", error); | |||
| // console.log("formik errors password", formik.errors.password); | |||
| // console.log("formik touched", formik.touched); | |||
| // console.log("formik.isVaid", formik); | |||
| // if (!formik.isValid) formik.setFieldValue("password", ""); | |||
| // // if (error?.length > 0 || formik.errors.password) { | |||
| // // formik.setFieldValue("password", ""); | |||
| // // console.log(formik.errors.password) | |||
| // // } | |||
| // }, [formik.isValid]) | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| console.dir(error); | |||
| }, [error]) | |||
| }, [error]); | |||
| return ( | |||
| <TextField | |||
| @@ -41,7 +30,8 @@ const PasswordField = forwardRef((props, ref) => { | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={ | |||
| (formik.touched.password && formik.errors.password?.length > 0) || error.length > 0 | |||
| (formik.touched.password && formik.errors.password?.length > 0) || | |||
| error.length > 0 | |||
| } | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| @@ -60,7 +60,6 @@ const Header = (props) => { | |||
| <> | |||
| <SkeletonHeader | |||
| skeleton={props.skeleton} | |||
| animationStage={props.animationStage} | |||
| /> | |||
| <HeaderContainer skeleton={props.skeleton}> | |||
| {/* Setting appropriate header title if page is market place or my offers */} | |||
| @@ -154,7 +153,6 @@ Header.propTypes = { | |||
| category: PropTypes.string, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| sorting: PropTypes.any, | |||
| }; | |||
| Header.defaultProps = { | |||
| @@ -5,13 +5,13 @@ import { CircleGroup, SkeletonHeaderCircle, SkeletonHeaderContainer, SkeletonHea | |||
| const SkeletonHeader = (props) => { | |||
| return ( | |||
| <SkeletonHeaderContainer skeleton={props.skeleton}> | |||
| <SkeletonHeaderLine animationStage={props.animationStage} /> | |||
| <SkeletonHeaderLine /> | |||
| <SkeletonRowGroup> | |||
| <CircleGroup> | |||
| <SkeletonHeaderCircle animationStage={props.animationStage} /> | |||
| <SkeletonHeaderCircle animationStage={props.animationStage} /> | |||
| <SkeletonHeaderCircle /> | |||
| <SkeletonHeaderCircle /> | |||
| </CircleGroup> | |||
| <SkeletonHeaderRightLine animationStage={props.animationStage} /> | |||
| <SkeletonHeaderRightLine /> | |||
| </SkeletonRowGroup> | |||
| </SkeletonHeaderContainer> | |||
| ) | |||
| @@ -19,7 +19,6 @@ const SkeletonHeader = (props) => { | |||
| SkeletonHeader.propTypes = { | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| } | |||
| export default SkeletonHeader | |||
| @@ -16,12 +16,10 @@ const MarketPlace = (props) => { | |||
| myOffers={props.myOffers} | |||
| sorting={props.offers.sorting} | |||
| skeleton={props.skeleton} | |||
| animationStage={props.animationStage} | |||
| /> | |||
| <Offers | |||
| isGrid={isGrid} | |||
| myOffers={props.myOffers} | |||
| animationStage={props.animationStage} | |||
| skeleton={props.skeleton} | |||
| offers={offers} | |||
| toggleFilters={props.toggleFilters} | |||
| @@ -33,7 +31,6 @@ const MarketPlace = (props) => { | |||
| MarketPlace.propTypes = { | |||
| children: PropTypes.node, | |||
| myOffers: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| skeleton: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| toggleFilters: PropTypes.func | |||
| @@ -73,7 +73,6 @@ const Offers = (props) => { | |||
| <SkeletonOfferCard | |||
| key={index} | |||
| skeleton | |||
| animationStage={props.animationStage} | |||
| /> | |||
| ))} | |||
| </> | |||
| @@ -87,7 +86,6 @@ Offers.propTypes = { | |||
| isGrid: PropTypes.bool, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| offers: PropTypes.any, | |||
| toggleFilters: PropTypes.func, | |||
| }; | |||
| @@ -1,7 +1,7 @@ | |||
| import React, { useEffect, useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ProfileContainer } from "./Profile.styled"; | |||
| import ProfileCard from "../ProfileCard/ProfileCard"; | |||
| import ProfileCard from "../Cards/ProfileCard/ProfileCard"; | |||
| import ProfileOffers from "./ProfileOffers/ProfileOffers"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| @@ -0,0 +1,26 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| HeaderTitleContainer, | |||
| HeaderTitleText, | |||
| OffersIcon, | |||
| } from "./HeaderTitle.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const HeaderTitle = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <HeaderTitleContainer container> | |||
| <OffersIcon /> | |||
| <HeaderTitleText> | |||
| {props.isMyProfile ? t("profile.myOffers") : t("profile.profileOffers")} | |||
| </HeaderTitleText> | |||
| </HeaderTitleContainer> | |||
| ); | |||
| }; | |||
| HeaderTitle.propTypes = { | |||
| isMyProfile: PropTypes.bool, | |||
| }; | |||
| export default HeaderTitle; | |||
| @@ -0,0 +1,32 @@ | |||
| import { Grid, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { ReactComponent as Package } from "../../../../assets/images/svg/package-gray.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const HeaderTitleText = styled(Typography)` | |||
| font-size: 16px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryDarkTextThird}; | |||
| position: relative; | |||
| margin-left: 10px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| } | |||
| `; | |||
| export const OffersIcon = styled(Package)` | |||
| width: 18px; | |||
| height: 18px; | |||
| & path { | |||
| stroke: ${selectedTheme.colors.primaryDarkTextThird}; | |||
| } | |||
| @media (max-width: 600px) { | |||
| width: 12px; | |||
| height: 12px; | |||
| } | |||
| `; | |||
| export const HeaderTitleContainer = styled(Grid)` | |||
| flex-direction: row; | |||
| justify-content: start; | |||
| align-items: center; | |||
| margin-bottom: 8px; | |||
| ` | |||
| @@ -1,26 +1,14 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| DownArrow, | |||
| HeaderSelect, | |||
| HeaderTitle, | |||
| IconContainer, | |||
| OffersContainer, | |||
| OffersIcon, | |||
| OffersScroller, | |||
| ProfileOffersContainer, | |||
| SearchIcon, | |||
| SearchInput, | |||
| SelectOption, | |||
| } from "./ProfileOffers.styled"; | |||
| import { Grid } from "@mui/material"; | |||
| import { useState } from "react"; | |||
| import { sortEnum } from "../../../enums/sortEnum"; | |||
| import { useEffect } from "react"; | |||
| import { useSelector } from "react-redux"; | |||
| import OfferCard from "../../Cards/OfferCard/OfferCard"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useRef } from "react"; | |||
| import { selectProfileOffers } from "../../../store/selectors/offersSelectors"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { useHistory } from "react-router-dom"; | |||
| @@ -31,19 +19,21 @@ import { OFFERS_PROFILE_SCOPE } from "../../../store/actions/offers/offersAction | |||
| import SkeletonOfferCard from "../../Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import ProfileOffersHeaderSkeleton from "./ProfileOffersHeaderSkeleton/ProfileOffersHeaderSkeleton"; | |||
| import SelectSortField from "./SelectSortField/SelectSortField"; | |||
| import HeaderTitle from "./HeaderTitle/HeaderTitle"; | |||
| import SearchBar from "./SearchBar/SearchBar"; | |||
| import { replaceInRoute } from "../../../util/helpers/routeHelpers"; | |||
| import { CHAT_MESSAGE_PAGE } from "../../../constants/pages"; | |||
| const ProfileOffers = (props) => { | |||
| const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | |||
| const [offersToShow, setOffersToShow] = useState([]); | |||
| const isLoadingMineOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE) | |||
| ); | |||
| const searchRef = useRef(null); | |||
| const chats = useSelector(selectLatestChats); | |||
| const profileOffers = useSelector(selectProfileOffers); | |||
| const { isMobile } = useIsMobile(); | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const userId = useSelector(selectUserId); | |||
| const arrayForMapping = Array.apply(null, Array(4)).map(() => {}); | |||
| @@ -52,133 +42,44 @@ const ProfileOffers = (props) => { | |||
| (item) => item.chat.offerId === offer?.offer?._id | |||
| ); | |||
| if (chatItem !== undefined) { | |||
| history.push(`/messages/${chatItem.chat._id}`); | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { idChat: chatItem.chat._id }) | |||
| ); | |||
| } else { | |||
| if (offer?.offer?.userId !== userId) { | |||
| history.push(`/messages/newMessage`, { | |||
| offerId: offer?.offer?._id, | |||
| }); | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { idChat: "newMessage" }), | |||
| { | |||
| offerId: offer?.offer?._id, | |||
| } | |||
| ); | |||
| } | |||
| } | |||
| }; | |||
| useEffect(() => { | |||
| let newOffersToShow = [...offersToShow]; | |||
| if (sortOption.value === sortEnum.OLD.value) { | |||
| newOffersToShow.sort( | |||
| (a, b) => new Date(a._created) - new Date(b._created) | |||
| ); | |||
| } | |||
| if (sortOption.value === sortEnum.NEW.value) { | |||
| newOffersToShow.sort( | |||
| (a, b) => new Date(b._created) - new Date(a._created) | |||
| ); | |||
| } | |||
| if (sortOption.value === sortEnum.POPULAR.value) { | |||
| newOffersToShow.sort( | |||
| (a, b) => a.views.viewers.length - b.views.viewers.length | |||
| ); | |||
| } | |||
| setOffersToShow([...newOffersToShow]); | |||
| }, [sortOption]); | |||
| useEffect(() => { | |||
| if (profileOffers) setOffersToShow(profileOffers); | |||
| }, [profileOffers]); | |||
| const handleSearch = () => { | |||
| const valueToSearch = searchRef?.current?.value; | |||
| const handleSearch = (value) => { | |||
| let newOffersToShow = profileOffers.filter((item) => | |||
| item.name.toLowerCase().includes(valueToSearch.toLowerCase()) | |||
| item.name.toLowerCase().includes(value.toLowerCase()) | |||
| ); | |||
| setOffersToShow([...newOffersToShow]); | |||
| }; | |||
| const handleChangeSelect = (event) => { | |||
| let chosenOption; | |||
| for (const sortOption in sortEnum) { | |||
| if (sortEnum[sortOption].value === event.target.value) { | |||
| chosenOption = sortEnum[sortOption]; | |||
| setSortOption(chosenOption); | |||
| } | |||
| } | |||
| }; | |||
| let listener; | |||
| const handleFocusSearch = () => { | |||
| listener = (event) => { | |||
| if (event.keyCode === 13) { | |||
| event.preventDefault(); | |||
| handleSearch(); | |||
| } | |||
| }; | |||
| searchRef.current.addEventListener("keyup", listener); | |||
| }; | |||
| const handleBlurSearch = () => { | |||
| searchRef.current.removeEventListener("keyup", listener); | |||
| }; | |||
| return ( | |||
| <ProfileOffersContainer> | |||
| {isLoadingMineOffers || isLoadingMineOffers === undefined ? ( | |||
| <ProfileOffersHeaderSkeleton /> | |||
| ) : ( | |||
| <> | |||
| <HeaderSelect | |||
| value={ | |||
| sortOption?.value ? sortOption.value : sortEnum.INITIAL.value | |||
| } | |||
| IconComponent={DownArrow} | |||
| onChange={handleChangeSelect} | |||
| > | |||
| <SelectOption | |||
| value={sortEnum.INITIAL.value} | |||
| key={sortEnum.INITIAL.value} | |||
| style={{ display: "none" }} | |||
| > | |||
| {sortEnum.INITIAL.mainText} | |||
| </SelectOption> | |||
| {Object.keys(sortEnum).map((property) => { | |||
| if (sortEnum[property].value === sortEnum.INITIAL.value) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sortEnum[property].value} | |||
| key={sortEnum[property].value} | |||
| > | |||
| {sortEnum[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelect> | |||
| <Grid | |||
| container | |||
| direction="row" | |||
| justifyContent="start" | |||
| alignItems="center" | |||
| sx={{ mb: 1.4 }} | |||
| > | |||
| <OffersIcon /> | |||
| <HeaderTitle> | |||
| {props.isMyProfile ? "Moje objave" : "Objave kompanije"} | |||
| </HeaderTitle> | |||
| </Grid> | |||
| <SearchInput | |||
| fullWidth | |||
| ref={searchRef} | |||
| onFocus={handleFocusSearch} | |||
| onBlur={handleBlurSearch} | |||
| // ref={searchRef} | |||
| placeholder={t("header.searchOffers")} | |||
| italicPlaceholder | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconContainer onClick={handleSearch}> | |||
| <SearchIcon /> | |||
| </IconContainer> | |||
| ), | |||
| }} | |||
| <SelectSortField | |||
| offersToShow={offersToShow} | |||
| setOffersToShow={setOffersToShow} | |||
| /> | |||
| <HeaderTitle isMyProfile={props.isMyProfile} /> | |||
| <SearchBar handleSearch={handleSearch} /> | |||
| </> | |||
| )} | |||
| <OffersContainer> | |||
| @@ -1,13 +1,5 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { ReactComponent as Search } from "../../../assets/images/svg/magnifying-glass.svg"; | |||
| import { ReactComponent as Package } from "../../../assets/images/svg/package-gray.svg"; | |||
| import { TextField } from "../../TextFields/TextField/TextField"; | |||
| import { Icon } from "../../Icon/Icon"; | |||
| import Select from "../../Select/Select"; | |||
| import Option from "../../Select/Option/Option"; | |||
| import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg"; | |||
| import HorizontalScroller from "../../Scroller/HorizontalScroller"; | |||
| export const ProfileOffersContainer = styled(Box)` | |||
| @@ -23,82 +15,6 @@ export const ProfileOffersContainer = styled(Box)` | |||
| padding: 0; | |||
| } | |||
| `; | |||
| export const HeaderTitle = styled(Typography)` | |||
| font-size: 16px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryDarkTextThird}; | |||
| position: relative; | |||
| margin-left: 10px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| } | |||
| `; | |||
| export const OffersIcon = styled(Package)` | |||
| width: 18px; | |||
| height: 18px; | |||
| & path { | |||
| stroke: ${selectedTheme.colors.primaryDarkTextThird}; | |||
| } | |||
| @media (max-width: 600px) { | |||
| width: 12px; | |||
| height: 12px; | |||
| } | |||
| `; | |||
| export const SearchInput = styled(TextField)` | |||
| position: relative; | |||
| top: 15px; | |||
| & div fieldset { | |||
| border-color: ${selectedTheme.colors.primaryPurple} !important; | |||
| } | |||
| @media (max-width: 600px) { | |||
| top: 5px; | |||
| height: 46px; | |||
| & div { | |||
| background-color: white; | |||
| } | |||
| } | |||
| `; | |||
| export const SearchIcon = styled(Search)` | |||
| width: 18px; | |||
| height: 18px; | |||
| `; | |||
| export const IconContainer = styled(Icon)` | |||
| cursor: pointer; | |||
| position: relative; | |||
| top: 4px; | |||
| `; | |||
| export const HeaderSelect = styled(Select)` | |||
| width: 210px; | |||
| height: 35px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| margin-top: 3px; | |||
| font-weight: 400; | |||
| position: absolute; | |||
| top: -8px; | |||
| right: 50px; | |||
| & div:first-child { | |||
| padding-left: 8px; | |||
| } | |||
| @media (max-width: 1200px) { | |||
| right: 36px; | |||
| } | |||
| @media (max-width: 650px) { | |||
| width: 144px; | |||
| height: 30px; | |||
| font-size: 14px; | |||
| right: 1px; | |||
| } | |||
| `; | |||
| export const SelectOption = styled(Option)` | |||
| @media (max-width: 600px) { | |||
| height: 20px !important; | |||
| min-height: 35px; | |||
| margin: 2px; | |||
| } | |||
| `; | |||
| export const DownArrow = styled(Down)``; | |||
| export const OffersContainer = styled(Box)` | |||
| margin-top: 30px; | |||
| `; | |||
| @@ -1,24 +1,32 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { ProfileOffersHeaderSkeletonContainer, ProfileOffersHeaderSkeletonGroupOne, ProfileOffersHeaderSkeletonGroupSecond, ProfileOffersHeaderSkeletonLineForth, ProfileOffersHeaderSkeletonLineOne, ProfileOffersHeaderSkeletonLineSecond, ProfileOffersHeaderSkeletonLineThird } from './ProfileOffersHeaderSkeleton.styled' | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ProfileOffersHeaderSkeletonContainer, | |||
| ProfileOffersHeaderSkeletonGroupOne, | |||
| ProfileOffersHeaderSkeletonGroupSecond, | |||
| ProfileOffersHeaderSkeletonLineForth, | |||
| ProfileOffersHeaderSkeletonLineOne, | |||
| ProfileOffersHeaderSkeletonLineSecond, | |||
| ProfileOffersHeaderSkeletonLineThird, | |||
| } from "./ProfileOffersHeaderSkeleton.styled"; | |||
| const ProfileOffersHeaderSkeleton = () => { | |||
| return ( | |||
| <ProfileOffersHeaderSkeletonContainer> | |||
| <ProfileOffersHeaderSkeletonGroupOne> | |||
| <ProfileOffersHeaderSkeletonLineOne /> | |||
| <ProfileOffersHeaderSkeletonLineSecond /> | |||
| </ProfileOffersHeaderSkeletonGroupOne> | |||
| <ProfileOffersHeaderSkeletonGroupSecond> | |||
| <ProfileOffersHeaderSkeletonLineThird /> | |||
| <ProfileOffersHeaderSkeletonLineForth /> | |||
| </ProfileOffersHeaderSkeletonGroupSecond> | |||
| <ProfileOffersHeaderSkeletonGroupOne> | |||
| <ProfileOffersHeaderSkeletonLineOne /> | |||
| <ProfileOffersHeaderSkeletonLineSecond /> | |||
| </ProfileOffersHeaderSkeletonGroupOne> | |||
| <ProfileOffersHeaderSkeletonGroupSecond> | |||
| <ProfileOffersHeaderSkeletonLineThird /> | |||
| <ProfileOffersHeaderSkeletonLineForth /> | |||
| </ProfileOffersHeaderSkeletonGroupSecond> | |||
| </ProfileOffersHeaderSkeletonContainer> | |||
| ) | |||
| } | |||
| ); | |||
| }; | |||
| ProfileOffersHeaderSkeleton.propTypes = { | |||
| children: PropTypes.node, | |||
| } | |||
| children: PropTypes.node, | |||
| }; | |||
| export default ProfileOffersHeaderSkeleton | |||
| export default ProfileOffersHeaderSkeleton; | |||
| @@ -3,7 +3,7 @@ import styled from "styled-components"; | |||
| import { | |||
| SkeletonBackgroundColor, | |||
| SkeletonItemBackgroundColor, | |||
| } from "../../../ProfileCard/SkeletonProfileCard/SkeletonProfileCard.styled"; | |||
| } from "../../../Cards/ProfileCard/SkeletonProfileCard/SkeletonProfileCard.styled"; | |||
| export const ProfileOffersHeaderSkeletonContainer = styled(Box)` | |||
| display: flex; | |||
| @@ -0,0 +1,53 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { IconContainer, SearchIcon, SearchInput } from "./SearchBar.styled"; | |||
| import { useCallback } from "react"; | |||
| import { useRef } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const SearchBar = (props) => { | |||
| const searchRef = useRef(null); | |||
| const { t } = useTranslation(); | |||
| let listener = useCallback( | |||
| (event) => { | |||
| if (event.keyCode === 13) { | |||
| event.preventDefault(); | |||
| props.handleSearch(searchRef.current?.value); | |||
| } | |||
| }, | |||
| [searchRef] | |||
| ); | |||
| const handleFocusSearch = () => { | |||
| searchRef.current.addEventListener("keyup", listener); | |||
| }; | |||
| const handleBlurSearch = () => { | |||
| searchRef.current.removeEventListener("keyup", listener); | |||
| }; | |||
| return ( | |||
| <SearchInput | |||
| fullWidth | |||
| ref={searchRef} | |||
| onFocus={handleFocusSearch} | |||
| onBlur={handleBlurSearch} | |||
| placeholder={t("header.searchOffers")} | |||
| italicPlaceholder | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconContainer | |||
| onClick={() => props.handleSearch(searchRef.current?.value)} | |||
| > | |||
| <SearchIcon /> | |||
| </IconContainer> | |||
| ), | |||
| }} | |||
| /> | |||
| ); | |||
| }; | |||
| SearchBar.propTypes = { | |||
| handleSearch: PropTypes.func, | |||
| }; | |||
| export default SearchBar; | |||
| @@ -0,0 +1,29 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { ReactComponent as Search } from "../../../../assets/images/svg/magnifying-glass.svg"; | |||
| import { Icon } from "../../../Icon/Icon"; | |||
| export const SearchInput = styled(TextField)` | |||
| position: relative; | |||
| top: 15px; | |||
| & div fieldset { | |||
| border-color: ${selectedTheme.colors.primaryPurple} !important; | |||
| } | |||
| @media (max-width: 600px) { | |||
| top: 5px; | |||
| height: 46px; | |||
| & div { | |||
| background-color: white; | |||
| } | |||
| } | |||
| `; | |||
| export const SearchIcon = styled(Search)` | |||
| width: 18px; | |||
| height: 18px; | |||
| `; | |||
| export const IconContainer = styled(Icon)` | |||
| cursor: pointer; | |||
| position: relative; | |||
| top: 4px; | |||
| `; | |||
| @@ -0,0 +1,76 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| DownArrow, | |||
| HeaderSelect, | |||
| SelectOption, | |||
| } from "./SelectSortField.styled"; | |||
| import { sortEnum } from "../../../../enums/sortEnum"; | |||
| import { useState } from "react"; | |||
| import { useEffect } from "react"; | |||
| const SelectSortField = (props) => { | |||
| const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | |||
| useEffect(() => { | |||
| let newOffersToShow = [...props.offersToShow]; | |||
| if (sortOption.value === sortEnum.OLD.value) { | |||
| newOffersToShow.sort( | |||
| (a, b) => new Date(a._created) - new Date(b._created) | |||
| ); | |||
| } | |||
| if (sortOption.value === sortEnum.NEW.value) { | |||
| newOffersToShow.sort( | |||
| (a, b) => new Date(b._created) - new Date(a._created) | |||
| ); | |||
| } | |||
| if (sortOption.value === sortEnum.POPULAR.value) { | |||
| newOffersToShow.sort((a, b) => b.views.count - a.views.count); | |||
| } | |||
| props.setOffersToShow([...newOffersToShow]); | |||
| }, [sortOption]); | |||
| const handleChangeSelect = (event) => { | |||
| let chosenOption; | |||
| console.log(sortOption); | |||
| for (const sortOption in sortEnum) { | |||
| if (sortEnum[sortOption].value === event.target.value) { | |||
| chosenOption = sortEnum[sortOption]; | |||
| console.log(chosenOption); | |||
| setSortOption(chosenOption); | |||
| } | |||
| } | |||
| }; | |||
| return ( | |||
| <HeaderSelect | |||
| value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value} | |||
| IconComponent={DownArrow} | |||
| onChange={handleChangeSelect} | |||
| > | |||
| <SelectOption | |||
| value={sortEnum.INITIAL.value} | |||
| key={sortEnum.INITIAL.value} | |||
| style={{ display: "none" }} | |||
| > | |||
| {sortEnum.INITIAL.mainText} | |||
| </SelectOption> | |||
| {Object.keys(sortEnum).map((property) => { | |||
| if (sortEnum[property].value === sortEnum.INITIAL.value) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sortEnum[property].value} | |||
| key={sortEnum[property].value} | |||
| > | |||
| {sortEnum[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelect> | |||
| ); | |||
| }; | |||
| SelectSortField.propTypes = { | |||
| offersToShow: PropTypes.array, | |||
| setOffersToShow: PropTypes.func, | |||
| }; | |||
| export default SelectSortField; | |||
| @@ -0,0 +1,38 @@ | |||
| import styled from "styled-components"; | |||
| import Select from "../../../Select/Select"; | |||
| import Option from "../../../Select/Option/Option"; | |||
| import { ReactComponent as Down } from "../../../../assets/images/svg/down-arrow.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const HeaderSelect = styled(Select)` | |||
| width: 210px; | |||
| height: 35px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| margin-top: 3px; | |||
| font-weight: 400; | |||
| position: absolute; | |||
| top: -8px; | |||
| right: 50px; | |||
| & div:first-child { | |||
| padding-left: 8px; | |||
| } | |||
| @media (max-width: 1200px) { | |||
| right: 36px; | |||
| } | |||
| @media (max-width: 650px) { | |||
| width: 144px; | |||
| height: 30px; | |||
| font-size: 14px; | |||
| right: 1px; | |||
| } | |||
| `; | |||
| export const SelectOption = styled(Option)` | |||
| @media (max-width: 600px) { | |||
| height: 20px !important; | |||
| min-height: 35px; | |||
| margin: 2px; | |||
| } | |||
| `; | |||
| export const DownArrow = styled(Down)``; | |||
| @@ -1,274 +0,0 @@ | |||
| import React, { useState, useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import BackdropComponent from "../../MUI/BackdropComponent"; | |||
| import { | |||
| EditProfileContainer, | |||
| ProfileImageContainer, | |||
| InputFieldLabel, | |||
| InputField, | |||
| BackButton, | |||
| CloseButton, | |||
| SaveButton, | |||
| ProfileHeader, | |||
| BasicInfo, | |||
| DetailsInfo, | |||
| ButtonsContainer, | |||
| ErrorMessage, | |||
| ProfileImagePicker, | |||
| InputFieldLabelLocation, | |||
| } from "./EditProfile.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useFormik } from "formik"; | |||
| import { ReactComponent as ArrowBack } from "../../../assets/images/svg/arrow-back.svg"; | |||
| import { ReactComponent as CloseIcon } from "../../../assets/images/svg/close-modal.svg"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| editMineProfile, | |||
| fetchMineProfile, | |||
| } from "../../../store/actions/profile/profileActions"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import editProfileValidation from "../../../validations/editProfileValidation"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import AutoSuggestTextField from "../../TextFields/AutoSuggestTextField/AutoSuggestTextField"; | |||
| import { selectLocations } from "../../../store/selectors/locationsSelectors"; | |||
| const EditProfile = (props) => { | |||
| const [profileImage, setProfileImage] = useState(props.profile.image); | |||
| const [showBasic, setShowBasic] = useState(true); | |||
| const [showDetails, setShowDetails] = useState(true); | |||
| const { t } = useTranslation(); | |||
| const dispatch = useDispatch(); | |||
| const { isMobile } = useIsMobile(); | |||
| const userId = useSelector(selectUserId); | |||
| const locations = useSelector(selectLocations); | |||
| useEffect(() => { | |||
| if (isMobile) { | |||
| setShowDetails(false); | |||
| } else { | |||
| setShowDetails(true); | |||
| } | |||
| }, [isMobile]); | |||
| const handleApiResponseSuccess = () => { | |||
| dispatch(fetchMineProfile(userId)); | |||
| props.reFetchProfile(); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| dispatch(editMineProfile({ ...values, handleApiResponseSuccess })); | |||
| props.closeModalHandler(); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| firmName: `${props?.profile?.company?.name}`, | |||
| firmPIB: `${props?.profile?.company?.PIB}`, | |||
| firmLocation: `${props?.profile?.company?.contacts?.location ?? ""}`, | |||
| firmWebsite: `${props?.profile?.company?.contacts?.web ?? ""}`, | |||
| firmApplink: "", | |||
| firmPhone: `${props?.profile?.company?.contacts?.telephone ?? ""}`, | |||
| firmLogo: profileImage, | |||
| }, | |||
| validationSchema: editProfileValidation, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| const closeEditModalHandler = () => { | |||
| props.closeModalHandler(); | |||
| }; | |||
| const showDetailsHandler = () => { | |||
| setShowDetails(!showDetails); | |||
| setShowBasic(!showBasic); | |||
| }; | |||
| const setImage = (image) => { | |||
| setProfileImage(image); | |||
| }; | |||
| return ( | |||
| <> | |||
| <BackdropComponent | |||
| handleClose={closeEditModalHandler} | |||
| isLoading | |||
| position="fixed" | |||
| /> | |||
| <EditProfileContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {!showBasic && ( | |||
| <BackButton onClick={showDetailsHandler}> | |||
| <ArrowBack /> | |||
| </BackButton> | |||
| )} | |||
| <ProfileImageContainer> | |||
| <ProfileImagePicker | |||
| image={profileImage} | |||
| setImage={setImage} | |||
| ></ProfileImagePicker> | |||
| <ProfileHeader>{props.profile.company.name}</ProfileHeader> | |||
| </ProfileImageContainer> | |||
| <CloseButton onClick={closeEditModalHandler}> | |||
| <CloseIcon /> | |||
| </CloseButton> | |||
| {showBasic && ( | |||
| <BasicInfo> | |||
| <InputFieldLabel leftText={t("common.labelFirm").toUpperCase()} /> | |||
| <InputField | |||
| name="firmName" | |||
| value={formik.values.firmName} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.firmName && formik.errors.firmName} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| <InputFieldLabel leftText={t("common.labelPIB")} /> | |||
| <InputField | |||
| name="firmPIB" | |||
| type="number" | |||
| value={formik.values.firmPIB} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.firmPIB && formik.errors.firmPIB} | |||
| margin="normal" | |||
| fullWidth | |||
| disabled | |||
| /> | |||
| <InputFieldLabelLocation | |||
| leftText={t("common.labelLocation").toUpperCase()} | |||
| /> | |||
| <AutoSuggestTextField | |||
| editLocation | |||
| data={locations.map((item) => ({ name: item.city }))} | |||
| value={formik.values.firmLocation} | |||
| onChange={(event, { newValue }) => | |||
| formik.setFieldValue("firmLocation", newValue) | |||
| } | |||
| /> | |||
| {/* <InputField | |||
| name="firmLocation" | |||
| value={formik.values.firmLocation} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.firmLocation && formik.errors.firmLocation} | |||
| margin="normal" | |||
| fullWidth | |||
| /> */} | |||
| </BasicInfo> | |||
| )} | |||
| {showDetails && ( | |||
| <DetailsInfo> | |||
| <InputFieldLabel | |||
| leftText={t("editProfile.website").toUpperCase()} | |||
| labelWebsite | |||
| /> | |||
| <InputField | |||
| name="firmWebsite" | |||
| value={formik.values.firmWebsite} | |||
| onChange={formik.handleChange} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| <InputFieldLabel | |||
| leftText={t("editProfile.applink").toUpperCase()} | |||
| /> | |||
| <InputField | |||
| name="firmApplink" | |||
| values={formik.values.firmApplink} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| <InputFieldLabel | |||
| leftText={t("editProfile.phoneNumber").toUpperCase()} | |||
| /> | |||
| <InputField | |||
| type="number" | |||
| name="firmPhone" | |||
| value={formik.values.firmPhone} | |||
| onChange={(event) => { | |||
| formik.setFieldValue("firmPhone", event.target.value); | |||
| }} | |||
| error={formik.touched.firmPhone && formik.errors.firmPhone} | |||
| margin="normal" | |||
| fullWidth | |||
| onInput={(e) => { | |||
| e.target.value = | |||
| e.target.value[0] === "0" && e.target.value.length > 1 | |||
| ? "0" + | |||
| String( | |||
| Math.max(0, parseInt(e.target.value)) | |||
| .toString() | |||
| .slice(0, 14) | |||
| ) | |||
| : Math.max(0, parseInt(e.target.value)) | |||
| .toString() | |||
| .slice(0, 14); | |||
| }} | |||
| /> | |||
| </DetailsInfo> | |||
| )} | |||
| {formik.errors.firmName && formik.touched.firmName ? ( | |||
| <ErrorMessage>{formik.errors.firmName}</ErrorMessage> | |||
| ) : formik.errors.firmPIB && formik.touched.firmPIB ? ( | |||
| <ErrorMessage>{formik.errors.firmPIB}</ErrorMessage> | |||
| ) : formik.errors.firmLocation && formik.touched.firmLocation ? ( | |||
| <ErrorMessage>{formik.errors.firmLocation}</ErrorMessage> | |||
| ) : formik.errors.firmPhone && formik.touched.firmPhone ? ( | |||
| <ErrorMessage>{formik.errors.firmPhone}</ErrorMessage> | |||
| ) : ( | |||
| <></> | |||
| )} | |||
| {!isMobile ? ( | |||
| <ButtonsContainer> | |||
| <SaveButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| width="335px" | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor="white" | |||
| > | |||
| {t("editProfile.saveChanges")} | |||
| </SaveButton> | |||
| </ButtonsContainer> | |||
| ) : ( | |||
| <ButtonsContainer> | |||
| <SaveButton | |||
| height="44px" | |||
| width="155px" | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor={selectedTheme.colors.primaryPurple} | |||
| onClick={showDetailsHandler} | |||
| > | |||
| {showDetails | |||
| ? t("editProfile.showBasic") | |||
| : t("editProfile.showDetails")} | |||
| </SaveButton> | |||
| <SaveButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="44px" | |||
| width="155px" | |||
| buttoncolor={selectedTheme.colors.primaryPurple} | |||
| textcolor="white" | |||
| > | |||
| {t("common.save")} | |||
| </SaveButton> | |||
| </ButtonsContainer> | |||
| )} | |||
| </EditProfileContainer> | |||
| </> | |||
| ); | |||
| }; | |||
| EditProfile.propTypes = { | |||
| children: PropTypes.node, | |||
| profile: PropTypes.any, | |||
| closeModalHandler: PropTypes.func, | |||
| setImage: PropTypes.func, | |||
| reFetchProfile: PropTypes.func, | |||
| }; | |||
| export default EditProfile; | |||
| @@ -0,0 +1,21 @@ | |||
| import React, { useMemo } from "react"; | |||
| import { Redirect, Route } from "react-router"; | |||
| import { useSelector } from "react-redux"; | |||
| import { BASE_PAGE } from "../../constants/pages"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| const AuthRoute = ({ ...props }) => { | |||
| const userId = useSelector(selectUserId); | |||
| const isUserAuthenticated = useMemo(() => { | |||
| if (userId?.length === 0) return false; | |||
| return true; | |||
| }, [userId]); | |||
| return !isUserAuthenticated ? ( | |||
| <Route {...props} /> | |||
| ) : ( | |||
| <Redirect to={BASE_PAGE} /> | |||
| ); | |||
| }; | |||
| export default AuthRoute; | |||
| @@ -100,9 +100,10 @@ export default { | |||
| welcome: "Dobro došli na trampu, želimo vam uspešno trampovanje!", | |||
| imageError: "Slika je obavezna!", | |||
| serverError: "Greška sa serverom!", | |||
| phoneNumberNoOfCharacters: "Broj telefona mora imati izmedju 6 i 15 karaktera!", | |||
| phoneNumberNoOfCharacters: | |||
| "Broj telefona mora imati izmedju 6 i 15 karaktera!", | |||
| locationError: "Odaberite ispravnu lokaciju!", | |||
| websiteError: "Unesite ispravnu adresu svog website!" | |||
| websiteError: "Unesite ispravnu adresu svog website!", | |||
| }, | |||
| forgotPassword: { | |||
| title: "Povrati lozinku", | |||
| @@ -171,11 +172,11 @@ export default { | |||
| newOffer: "Nova Objava", | |||
| product: "Proizvod", | |||
| descriptionLabel: "Opis:", | |||
| checkButtonLabel: "Pogledaj proizvod" | |||
| checkButtonLabel: "Pogledaj proizvod", | |||
| }, | |||
| apiErrors: { | |||
| somethingWentWrong: "Greška sa serverom!", | |||
| offerNotFound: "Ponuda nije pronađena!" | |||
| offerNotFound: "Ponuda nije pronađena!", | |||
| }, | |||
| header: { | |||
| addOffer: "Dodaj proizvod", | |||
| @@ -205,7 +206,7 @@ export default { | |||
| leaveComment: "Ostavi komentar", | |||
| rates: "Ocene kompanije", | |||
| finishedReviewTitle: "Hvala vam", | |||
| finishedReviewAltTitle: "na izdvojenom vremenu i datoj oceni!" | |||
| finishedReviewAltTitle: "na izdvojenom vremenu i datoj oceni!", | |||
| }, | |||
| messages: { | |||
| headerTitle: "Moje Ćaskanje", | |||
| @@ -214,7 +215,7 @@ export default { | |||
| send: "Pošalji", | |||
| sendPlaceholder: "Poruka...", | |||
| seeChats: "Pogledaj ćaskanje", | |||
| noMessagesToast: "Nemate ni jednu poruku!" | |||
| noMessagesToast: "Nemate ni jednu poruku!", | |||
| }, | |||
| editProfile: { | |||
| website: "Web Sajt*", | |||
| @@ -270,11 +271,13 @@ export default { | |||
| altText: "Nažalost nemate ni jednu objavu", | |||
| }, | |||
| backToHome: "Nazad na sve objave", | |||
| myOffers: "Moje objave", | |||
| profileOffers: "Objave kompanije", | |||
| }, | |||
| about: { | |||
| header: { | |||
| title: "O Trampi", | |||
| navigation: 'O nama', | |||
| navigation: "O nama", | |||
| paragraph: | |||
| "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ac augue tortor. Nulla facilisi. Cras vestibulum risus eget tincidunt egestas. Duis blandit enim sit amet dui vehicula luctus. Suspendisse id blandit arcu, vitae consequat nisi. Duis ut vestibulum tellus. Curabitur eu fringilla nisi.", | |||
| }, | |||
| @@ -315,7 +318,7 @@ export default { | |||
| prices: { | |||
| header: { | |||
| title: "Cenovnik", | |||
| navigation: 'Cenovnik', | |||
| navigation: "Cenovnik", | |||
| link: "Pročitajte opširnije o našim ponudama", | |||
| }, | |||
| altText: | |||
| @@ -369,7 +372,7 @@ export default { | |||
| }, | |||
| aboutFooter: { | |||
| goStart: "Početak stranice", | |||
| middleText: "Diligent Software © 2022 | Sva prava zadržana" | |||
| middleText: "Diligent Software © 2022 | Sva prava zadržana", | |||
| }, | |||
| notFoundData: { | |||
| PIB: "PIB broj", | |||
| @@ -380,6 +383,6 @@ export default { | |||
| offerName: "Naslov ponude", | |||
| condition: "Stanje", | |||
| description: "Opis", | |||
| email: "Mejl kompanije" | |||
| } | |||
| email: "Mejl kompanije", | |||
| }, | |||
| }; | |||
| @@ -0,0 +1,9 @@ | |||
| export default (profile) => ({ | |||
| firmName: profile?.company?.name, | |||
| firmPIB: profile?.company?.PIB, | |||
| firmLocation: profile?.company?.contacts?.location ?? "", | |||
| firmWebsite: profile?.company?.contacts?.web ?? "", | |||
| firmApplink: "", | |||
| firmPhone: profile?.company?.contacts?.telephone ?? "", | |||
| firmLogo: profile?.image, | |||
| }) | |||
| @@ -1,25 +1,25 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { Content, RightCard, ProfileLayoutContainer, HeaderCard, MiddleCard } from "./ProfileLayout.styled"; | |||
| import { Grid } from "@mui/material"; | |||
| import { | |||
| ContentRightCardContainer, | |||
| Content, | |||
| RightCard, | |||
| ProfileLayoutContainer, | |||
| } from "./ProfileLayout.styled"; | |||
| const ProfileLayout = (props) => { | |||
| return ( | |||
| <ProfileLayoutContainer> | |||
| <ProfileLayoutContainer | |||
| singleOffer={props.singleOffer} | |||
| profile={props.profile} | |||
| > | |||
| {props.children} | |||
| <Grid container maxHeight maxWidth={1900}> | |||
| <MiddleCard item xs={9.5} lg={6.5} xl={6.6} md={6}> | |||
| <HeaderCard> | |||
| {props.headerCard} | |||
| </HeaderCard> | |||
| <Content> | |||
| {props.content} | |||
| </Content> | |||
| </MiddleCard> | |||
| <RightCard item xs={2.5} lg={3} xl={3} md={3}> | |||
| {props.rightCard} | |||
| <ContentRightCardContainer> | |||
| <Content item>{props.content}</Content> | |||
| <RightCard item singleOffer={props.singleOffer} profile={props.profile}> | |||
| {props.rightCard} | |||
| </RightCard> | |||
| </Grid> | |||
| </ContentRightCardContainer> | |||
| </ProfileLayoutContainer> | |||
| ); | |||
| }; | |||
| @@ -29,7 +29,8 @@ ProfileLayout.propTypes = { | |||
| leftCard: PropTypes.node, | |||
| content: PropTypes.node, | |||
| rightCard: PropTypes.node, | |||
| headerCard: PropTypes.node, | |||
| singleOffer: PropTypes.bool, | |||
| profile: PropTypes.bool, | |||
| }; | |||
| export default ProfileLayout; | |||
| @@ -1,38 +1,51 @@ | |||
| import { Box, Container, Grid } from "@mui/material"; | |||
| import { Container, Grid, Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const ProfileLayoutContainer = styled(Container)` | |||
| padding-left: 0; | |||
| padding-right: 0; | |||
| margin: 0; | |||
| width: 100%; | |||
| display: flex; | |||
| max-width: none; | |||
| flex: 1; | |||
| height: 100%; | |||
| margin-top: 80px; | |||
| @media (max-width: 600px) { | |||
| margin-top: 40px; | |||
| } | |||
| ` | |||
| padding-left: 36px; | |||
| padding-right: ${(props) => (props.singleOffer ? "76px" : 0)}; | |||
| margin: 0; | |||
| width: 100%; | |||
| max-width: none; | |||
| /* display: flex; */ | |||
| position: relative; | |||
| /* flex: 1; */ | |||
| height: 100%; | |||
| @media (max-width: 1200px) { | |||
| padding-right: ${(props) => (props.profile ? 0 : "36px")}; | |||
| } | |||
| @media (max-width: 600px) { | |||
| padding-left: 18px; | |||
| padding-right: ${(props) => (props.profile ? 0 : "18px")}; | |||
| } | |||
| `; | |||
| export const LeftCard = styled(Grid)` | |||
| margin-top: 30px; | |||
| export const ContentRightCardContainer = styled(Box)` | |||
| display: flex; | |||
| @media screen and (max-width: 600px) { | |||
| flex-direction: column; | |||
| } | |||
| `; | |||
| export const RightCard = styled(Grid)` | |||
| margin-top: 0; | |||
| margin-left: 0; | |||
| padding-left: 0; | |||
| ${(props) => props.profile && `min-width: 350px;`} | |||
| @media screen and (min-width: 600px) { | |||
| margin-top: 34px; | |||
| margin-left: ${(props) => (props.profile ? "0" : "36px")}; | |||
| padding-left: ${(props) => (props.singleOffer ? "36px" : 0)}; | |||
| border-top-right-radius: 4px; | |||
| background-color: green; | |||
| ` | |||
| ${(props) => props.singleOffer && `width: 100%`} | |||
| } | |||
| @media screen and (max-width: 1200px) { | |||
| margin-left: 0; | |||
| } | |||
| `; | |||
| export const Content = styled(Grid)` | |||
| background-color: yellow; | |||
| height: 100%; | |||
| ` | |||
| export const RightCard = styled(Grid)` | |||
| margin-top: 30px; | |||
| border-top-left-radius: 4px; | |||
| background-color: blue; | |||
| ` | |||
| export const MiddleCard = styled(Grid)` | |||
| ` | |||
| export const HeaderCard = styled(Box)` | |||
| height: 450px; | |||
| background-color: orange; | |||
| ` | |||
| width: 100%; | |||
| `; | |||
| @@ -7,7 +7,6 @@ import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelect | |||
| import { useSelector } from "react-redux"; | |||
| import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| import useOffers from "../../hooks/useOffers/useOffers"; | |||
| import useSkeleton from "../../hooks/useSkeleton"; | |||
| const HomePage = () => { | |||
| const isLoadingOffers = useSelector( | |||
| @@ -15,10 +14,6 @@ const HomePage = () => { | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const offers = useOffers(); | |||
| const { transitionStage } = useSkeleton({ | |||
| timeoutInterval: 900, | |||
| isLoadingIndicator: isLoadingOffers, | |||
| }); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| @@ -31,7 +26,6 @@ const HomePage = () => { | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| animationStage={transitionStage} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| @@ -39,7 +33,6 @@ const HomePage = () => { | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| animationStage={transitionStage} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| @@ -5,7 +5,6 @@ import MainLayout from "../../layouts/MainLayout/MainLayout"; | |||
| import FilterCard from "../../components/Cards/FilterCard/FilterCard"; | |||
| import MarketPlace from "../../components/MarketPlace/MarketPlace"; | |||
| import useMyOffers from "../../hooks/useOffers/useMyOffers"; | |||
| import useSkeleton from "../../hooks/useSkeleton"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { OFFERS_MINE_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| @@ -17,10 +16,6 @@ const MyOffers = () => { | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const { transitionStage } = useSkeleton({ | |||
| timeoutInterval: 900, | |||
| isLoadingIndicator: isLoadingMineOffers, | |||
| }); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| @@ -31,7 +26,6 @@ const MyOffers = () => { | |||
| <FilterCard | |||
| myOffers | |||
| offers={offers} | |||
| animationStage={transitionStage} | |||
| filtersOpened={filtersOpened} | |||
| toggleFilters={toggleFilters} | |||
| skeleton={isLoadingMineOffers} | |||
| @@ -41,7 +35,6 @@ const MyOffers = () => { | |||
| <MarketPlace | |||
| myOffers={true} | |||
| offers={offers} | |||
| animationStage={transitionStage} | |||
| skeleton={isLoadingMineOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| @@ -1,5 +1,5 @@ | |||
| import React from "react"; | |||
| import ItemDetailsLayout from "../../layouts/ItemDetailsLayout/ItemDetailsLayout"; | |||
| import ProfileLayout from "../../layouts/ProfileLayout/ProfileLayout"; | |||
| import { ProfilePageContainer } from "./ProfilePage.styled"; | |||
| import Profile from "../../components/Profile/Profile"; | |||
| import UserReviews from "../../components/UserReviews/UserReviews"; | |||
| @@ -7,7 +7,11 @@ import UserReviews from "../../components/UserReviews/UserReviews"; | |||
| const ProfilePage = () => { | |||
| return ( | |||
| <ProfilePageContainer> | |||
| <ItemDetailsLayout content={<Profile/>} rightCard={<UserReviews isProfileReviews/>} profile /> | |||
| <ProfileLayout | |||
| content={<Profile />} | |||
| rightCard={<UserReviews isProfileReviews />} | |||
| profile | |||
| /> | |||
| </ProfilePageContainer> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| CLEAR_LOGIN_USER_ERROR, | |||
| LOGIN_USER_ERROR, | |||
| @@ -7,21 +7,18 @@ import { | |||
| UPDATE_USER_JWT_TOKEN, | |||
| GENERATE_TOKEN_SUCCESS, | |||
| GENERATE_TOKEN_ERROR, | |||
| } from '../../actions/login/loginActionConstants'; | |||
| } from "../../actions/login/loginActionConstants"; | |||
| const initialState = { | |||
| email: '', | |||
| token: { | |||
| RefreshToken: '', | |||
| JwtToken: '', | |||
| userId: '' | |||
| }, | |||
| errorMessage: '', | |||
| email: "", | |||
| jwtToken: {}, | |||
| refreshToken: {}, | |||
| userId: "", | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [LOGIN_USER_SUCCESS]: setUser, | |||
| [UPDATE_USER_JWT_TOKEN]: setUserJwtToken, | |||
| [RESET_LOGIN_STATE]: resetLoginState, | |||
| @@ -30,24 +27,20 @@ export default createReducer( | |||
| [GENERATE_TOKEN_SUCCESS]: generateToken, | |||
| [GENERATE_TOKEN_ERROR]: generateTokenError, | |||
| }, | |||
| initialState, | |||
| initialState | |||
| ); | |||
| function setUser(state, action) { | |||
| return { | |||
| ...state, | |||
| token: action.payload, | |||
| ...action.payload, | |||
| }; | |||
| } | |||
| function setUserJwtToken(state, action) { | |||
| return { | |||
| ...state, | |||
| token: { | |||
| ...state.token, | |||
| JwtToken: action.payload, | |||
| }, | |||
| jwtToken: action.payload, | |||
| }; | |||
| } | |||
| @@ -65,7 +58,7 @@ function resetLoginState() { | |||
| function clearLoginErrors(state) { | |||
| return { | |||
| ...state, | |||
| errorMessage: '', | |||
| errorMessage: "", | |||
| }; | |||
| } | |||
| @@ -59,7 +59,7 @@ function* fetchLogin({ payload }) { | |||
| yield call(addHeaderToken, token); | |||
| const profileData = yield call(attemptFetchProfile, userId); | |||
| if (profileData) yield put(setMineProfile(profileData.data)); | |||
| yield put(fetchUserSuccess({JwtToken: accessToken, RefreshToken: refreshToken, userId})); | |||
| yield put(fetchUserSuccess({jwtToken: accessToken, refreshToken: refreshToken, userId})); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| @@ -60,8 +60,8 @@ function* fetchRegisterUser({ payload }) { | |||
| if (profileData) yield put(setMineProfile(profileData.data)); | |||
| yield put( | |||
| fetchUserSuccess({ | |||
| JwtToken: accessToken, | |||
| RefreshToken: refreshToken, | |||
| jwtToken: accessToken, | |||
| refreshToken: refreshToken, | |||
| userId, | |||
| }) | |||
| ); | |||
| @@ -1,31 +1,11 @@ | |||
| import { createSelector } from 'reselect'; | |||
| import { createSelector } from "reselect"; | |||
| const loginSelector = (state) => state.login; | |||
| export const selectLoginEmail = createSelector( | |||
| loginSelector, | |||
| (state) => state.email, | |||
| ); | |||
| export const selectUsernames = createSelector( | |||
| loginSelector, | |||
| (state) => state.usernames, | |||
| ); | |||
| export const selectTokens = createSelector( | |||
| loginSelector, | |||
| (state) => state.token, | |||
| ); | |||
| export const selectJWTToken = createSelector( | |||
| loginSelector, | |||
| (state) => state.token, | |||
| ); | |||
| export const selectUserId = createSelector( | |||
| loginSelector, | |||
| (state) => state.token.userId | |||
| ) | |||
| (state) => state.userId | |||
| ); | |||
| export const selectLoginError = createSelector( | |||
| loginSelector, | |||
| (state) => state.errorMessage, | |||
| (state) => state.errorMessage | |||
| ); | |||
| @@ -1,3 +1,11 @@ | |||
| import { | |||
| FORGOT_PASSWORD_MAIL_SENT, | |||
| FORGOT_PASSWORD_PAGE, | |||
| LOGIN_PAGE, | |||
| REGISTER_PAGE, | |||
| REGISTER_SUCCESSFUL_PAGE, | |||
| RESET_PASSWORD_PAGE, | |||
| } from "../../constants/pages"; | |||
| import history from "../../store/utils/history"; | |||
| export const routeMatches = (route) => { | |||
| @@ -18,3 +26,16 @@ export const replaceInRoute = (route, pathVariables = {}) => { | |||
| route | |||
| ); | |||
| }; | |||
| export const isAuthRoute = () => { | |||
| if ( | |||
| routeMatches(LOGIN_PAGE) || | |||
| routeMatches(REGISTER_PAGE) || | |||
| routeMatches(REGISTER_SUCCESSFUL_PAGE) || | |||
| routeMatches(FORGOT_PASSWORD_PAGE) || | |||
| routeMatches(FORGOT_PASSWORD_MAIL_SENT) || | |||
| routeMatches(RESET_PASSWORD_PAGE) | |||
| ) { | |||
| return true; | |||
| } | |||
| return false; | |||
| }; | |||