| @@ -3,25 +3,30 @@ | |||
| <head> | |||
| <meta charset="utf-8" /> | |||
| <link rel="icon" href="%PUBLIC_URL%/favicon.png" /> | |||
| <link | |||
| <!-- <link | |||
| rel="stylesheet" | |||
| type="text/css" | |||
| href="https://fonts.googleapis.com/css?family=Open+Sans:wght@700;600;500;400;300" | |||
| /> | |||
| href="https://fonts.googleapis.com/css?family=Open+Sans:wght@700;600;500;400;300&display=swap" | |||
| /> --> | |||
| <link | |||
| rel="stylesheet" | |||
| type="text/css" | |||
| href="https://fonts.googleapis.com/css?family=Poppins" | |||
| rel="preload" | |||
| as="style" | |||
| href="https://fonts.googleapis.com/css?family=Poppins&display=swap" | |||
| onload="this.onload=null;this.rel='stylesheet';this.type='text/css'" | |||
| /> | |||
| <link | |||
| rel="stylesheet" | |||
| type="text/css" | |||
| href="https://fonts.googleapis.com/css?family=Mulish" | |||
| style | |||
| rel="preload" | |||
| as="style" | |||
| href="https://fonts.googleapis.com/css?family=Mulish&display=swap" | |||
| onload="this.onload=null;this.rel='stylesheet';this.type='text/css'" | |||
| /> | |||
| <link | |||
| rel="stylesheet" | |||
| type="text/css" | |||
| href="https://fonts.googleapis.com/css?family=DM+Sans:300,400,500,600,700,800" | |||
| style | |||
| rel="preload" | |||
| as="style" | |||
| href="https://fonts.googleapis.com/css?family=DM+Sans:300,400,500,600,700,800&display=swap" | |||
| onload="this.onload=null;this.rel='stylesheet';this.type='text/css'" | |||
| /> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
| <meta name="theme-color" content="#000000" /> | |||
| @@ -1,19 +1,20 @@ | |||
| /*eslint-disable*/ | |||
| import React, { useState, useEffect } from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import { Router } from "react-router-dom"; | |||
| import { Helmet } from "react-helmet-async"; | |||
| import { ToastContainer } from "react-toastify"; | |||
| import { StyledEngineProvider } from "@mui/material"; | |||
| import { useSelector } from "react-redux"; | |||
| import i18next from "i18next"; | |||
| import { selectUserId } from "./store/selectors/loginSelectors"; | |||
| import history from "./store/utils/history"; | |||
| import AppRoutes from "./AppRoutes"; | |||
| import { socketInit } from "./socket/socket"; | |||
| import Header from "./components/Header/Header"; | |||
| import { StyledEngineProvider } from "@mui/material"; | |||
| import GlobalStyle from "./components/Styles/globalStyles"; | |||
| import { ToastContainer } from "react-toastify"; | |||
| import "react-toastify/dist/ReactToastify.css"; | |||
| import { socketInit } from "./socket/socket"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "./store/selectors/loginSelectors"; | |||
| import Modal from "./components/Modals/Modal"; | |||
| import "react-toastify/dist/ReactToastify.css"; | |||
| const App = () => { | |||
| const userId = useSelector(selectUserId); | |||
| @@ -1,7 +1,5 @@ | |||
| /* eslint-disable */ | |||
| import React from "react"; | |||
| import { Redirect, Route, Switch, useLocation } from "react-router-dom"; | |||
| import { Redirect, Route, Switch } from "react-router-dom"; | |||
| import { | |||
| LOGIN_PAGE, | |||
| ADMIN_LOGIN_PAGE, | |||
| @@ -17,21 +15,15 @@ import { | |||
| ITEM_DETAILS_PAGE, | |||
| FORGOT_PASSWORD_PAGE, | |||
| PROFILE_PAGE, | |||
| CHAT_MESSAGE_PAGE, | |||
| CHAT_PAGE, | |||
| MY_OFFERS_PAGE, | |||
| // PRICES_PAGE, | |||
| ABOUT_PAGE, | |||
| ADMIN_HOME_PAGE, | |||
| ADMIN_USERS_PAGE, | |||
| ADMIN_CATEGORIES_PAGE, | |||
| ADMIN_SUBCATEGORIES_PAGE, | |||
| // POLICY_PRIVACY_PAGE, | |||
| MESSAGES_LIST_PAGE, | |||
| DIRECT_CHAT_PAGE, | |||
| } from "./constants/pages"; | |||
| // import SlideRoutes from "react-slide-routes"; | |||
| import LoginPage from "./pages/LoginPage/LoginPage"; | |||
| import AdminLoginPage from "./pages/AdminLoginPage/AdminLoginPage"; | |||
| import HomePage from "./pages/HomePage/HomePageMUI"; | |||
| import HomePage from "./pages/HomePage/HomePage"; | |||
| import NotFoundPage from "./pages/ErrorPages/NotFoundPage"; | |||
| import ErrorPage from "./pages/ErrorPages/ErrorPage"; | |||
| import ForgotPasswordPage from "./pages/ForgotPasswordPage/ForgotPasswordPage"; | |||
| @@ -43,48 +35,36 @@ import ResetPasswordPage from "./pages/ResetPasswordPage/ResetPasswordPage"; | |||
| import CreateOffer from "./pages/CreateOffer/CreateOffer"; | |||
| import ItemDetailsPage from "./pages/ItemDetailsPage/ItemDetailsPageMUI"; | |||
| import ProfilePage from "./pages/ProfilePage/ProfilePage"; | |||
| import ChatMessagesPage from "./pages/ChatMessages/ChatMessages"; | |||
| import ChatPage from "./pages/Chat/Chat"; | |||
| import DirectChatPage from "./pages/DirectChatPage/DirectChatPage"; | |||
| import MessagesListPage from "./pages/MessagesListPage/MessagesListPage"; | |||
| 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 AdminRoute from "./components/Router/AdminRoute"; | |||
| import AdminHomePage from "./pages/AdminHomePage/AdminHomePage"; | |||
| // import PrivacyPolicyPage from "./pages/PrivacyPolicy/PrivacyPolicyPage"; | |||
| const AppRoutes = () => { | |||
| // const location = useLocation(); | |||
| return ( | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||
| <AuthRoute exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <AuthRoute exact path={ADMIN_LOGIN_PAGE} component={AdminLoginPage} /> | |||
| <AdminRoute path={ADMIN_HOME_PAGE} component={AdminHomePage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <Route 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={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| <Route path={CREATE_OFFER_PAGE} component={CreateOffer} /> | |||
| <Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} /> | |||
| <Route path={PROFILE_PAGE} component={ProfilePage} /> | |||
| {/* <Route path={PRICES_PAGE} component={PricesPage} /> */} | |||
| <Route path={ABOUT_PAGE} component={AboutPage} /> | |||
| {/* <Route path={POLICY_PRIVACY_PAGE} component={PrivacyPolicyPage} /> */} | |||
| <Route | |||
| path={HOME_PAGE} | |||
| component={(props) => { | |||
| return <HomePage key={props.match.params.id} />; | |||
| }} | |||
| /> | |||
| {/* <SlideRoutes location={location}> */} | |||
| <PrivateRoute exact path={CHAT_PAGE} component={ChatPage} /> | |||
| <PrivateRoute path={CHAT_MESSAGE_PAGE} component={ChatMessagesPage} /> | |||
| {/* </SlideRoutes> */} | |||
| <Route path={HOME_PAGE} component={HomePage} /> | |||
| <PrivateRoute exact path={MESSAGES_LIST_PAGE} component={MessagesListPage} /> | |||
| <PrivateRoute path={DIRECT_CHAT_PAGE} component={DirectChatPage} /> | |||
| <PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} /> | |||
| <AdminRoute path={ADMIN_HOME_PAGE} component={AdminHomePage} /> | |||
| <Route path={ERROR_PAGE} component={ErrorPage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Redirect from="*" to={NOT_FOUND_PAGE} /> | |||
| </Switch> | |||
| ); | |||
| @@ -18,6 +18,7 @@ export const IconButton = (props) => { | |||
| onClick={props.onClick} | |||
| sx={props.style} | |||
| iconcolor={props.iconColor} | |||
| {...props} | |||
| > | |||
| {props.children} | |||
| </IconButtonStyled> | |||
| @@ -29,7 +29,7 @@ const FilterCard = (props) => { | |||
| myOffers={props.myOffers} | |||
| skeleton={props.skeleton} | |||
| > | |||
| <SkeletonFilterCard skeleton={props.skeleton} /> | |||
| {props?.skeleton && <SkeletonFilterCard skeleton={props.skeleton} />} | |||
| {/* Header title for my offers */} | |||
| {props.myOffers && <HeaderBack />} | |||
| @@ -73,18 +73,19 @@ const FilterCheckboxDropdown = (props) => { | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| > | |||
| {dataToShow.map((item) => { | |||
| return ( | |||
| <DropdownItem key={item.city}> | |||
| <Checkbox | |||
| item={item} | |||
| filters={props.filters} | |||
| onChange={() => handleChange(item)} | |||
| companies={props.companies} | |||
| /> | |||
| </DropdownItem> | |||
| ); | |||
| })} | |||
| {(isOpened || props?.open) && | |||
| dataToShow.map((item) => { | |||
| return ( | |||
| <DropdownItem key={item.city}> | |||
| <Checkbox | |||
| item={item} | |||
| filters={props.filters} | |||
| onChange={() => handleChange(item)} | |||
| companies={props.companies} | |||
| /> | |||
| </DropdownItem> | |||
| ); | |||
| })} | |||
| </CheckboxDropdownList> | |||
| ); | |||
| }; | |||
| @@ -54,27 +54,31 @@ const FilterSubDropdown = (props) => { | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| > | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.firstSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.secondSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.thirdSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| {(isOpened || props?.open) && ( | |||
| <> | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.firstSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.secondSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| <FilterSmallDropdown | |||
| letters={t("filters.company.thirdSort")} | |||
| dataToShow={dataToShow} | |||
| filters={props.filters} | |||
| setItemsSelected={props.setItemsSelected} | |||
| companies={props.companies} | |||
| /> | |||
| </> | |||
| )} | |||
| </CheckboxDropdownList> | |||
| ); | |||
| }; | |||
| @@ -37,7 +37,8 @@ const FilterRadioDropdown = (props) => { | |||
| if ( | |||
| props.selected?._id !== 0 && | |||
| props.selected !== null && | |||
| props.selected !== undefined | |||
| props.selected !== undefined && | |||
| Object.keys(props.selected).length !== 0 | |||
| ) { | |||
| setIsOpened(true); | |||
| } | |||
| @@ -101,39 +102,41 @@ const FilterRadioDropdown = (props) => { | |||
| </React.Fragment> | |||
| } | |||
| > | |||
| <RadioGroup> | |||
| {props.firstOption && ( | |||
| <DropdownItem> | |||
| <RadioButton | |||
| value={props.firstOption.value} | |||
| label={props.firstOption.label} | |||
| // number={item.numberOfProducts} | |||
| fullWidth | |||
| checked={!props.selected || props.selected._id === 0} | |||
| onChange={props.setSelected} | |||
| /> | |||
| </DropdownItem> | |||
| )} | |||
| {dataToShow.map((item) => { | |||
| return ( | |||
| <DropdownItem | |||
| key={item.name} | |||
| onClick={() => props.setSelected(item)} | |||
| > | |||
| {(isOpened || props?.open) && ( | |||
| <RadioGroup> | |||
| {props.firstOption && ( | |||
| <DropdownItem> | |||
| <RadioButton | |||
| value={item} | |||
| label={item?.name ? item?.name : item?.length > 0 ? item : ""} | |||
| number={item.offerCount} | |||
| value={props.firstOption.value} | |||
| label={props.firstOption.label} | |||
| // number={item.numberOfProducts} | |||
| fullWidth | |||
| checked={ | |||
| JSON.stringify(props.selected) === JSON.stringify(item) | |||
| } | |||
| checked={!props.selected || props.selected._id === 0} | |||
| onChange={props.setSelected} | |||
| /> | |||
| </DropdownItem> | |||
| ); | |||
| })} | |||
| </RadioGroup> | |||
| )} | |||
| {dataToShow.map((item) => { | |||
| return ( | |||
| <DropdownItem | |||
| key={item.name} | |||
| onClick={() => props.setSelected(item)} | |||
| > | |||
| <RadioButton | |||
| value={item} | |||
| label={item?.name ? item?.name : item?.length > 0 ? item : ""} | |||
| number={item.offerCount} | |||
| fullWidth | |||
| checked={ | |||
| JSON.stringify(props.selected) === JSON.stringify(item) | |||
| } | |||
| onChange={props.setSelected} | |||
| /> | |||
| </DropdownItem> | |||
| ); | |||
| })} | |||
| </RadioGroup> | |||
| )} | |||
| </DropdownList> | |||
| ); | |||
| }; | |||
| @@ -97,7 +97,6 @@ const ItemDetailsCard = (props) => { | |||
| ); | |||
| }; | |||
| const showPinOfferModalHandler = () => { | |||
| console.log(offer); | |||
| dispatch( | |||
| toggleDeleteOfferModal({ | |||
| offer: offer?.offer, | |||
| @@ -71,6 +71,7 @@ const OfferDetails = (props) => { | |||
| ? images[index] | |||
| : getImageUrl(item, variants.offerCard, isMobile) | |||
| } | |||
| alt={t("offer.imageAlt")} | |||
| key={item} | |||
| previewCard={props.previewCard} | |||
| onClick={() => | |||
| @@ -14,7 +14,7 @@ import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| import history from "../../../store/utils/history"; | |||
| import { replaceInRoute } from "../../../util/helpers/routeHelpers"; | |||
| import { CHAT_MESSAGE_PAGE } from "../../../constants/pages"; | |||
| import { DIRECT_CHAT_PAGE } from "../../../constants/pages"; | |||
| import BlockedProfile from "../ProfileCard/BlockedProfile/BlockedProfile"; | |||
| const MiniChatCard = (props) => { | |||
| @@ -22,7 +22,7 @@ const MiniChatCard = (props) => { | |||
| const { isMobile } = useIsMobile(); | |||
| const changeChat = () => { | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { | |||
| replaceInRoute(DIRECT_CHAT_PAGE, { | |||
| idChat: props?.chat?.chat?._id, | |||
| }) | |||
| ); | |||
| @@ -128,7 +128,6 @@ const OfferCard = (props) => { | |||
| const acceptExchange = (event) => { | |||
| event.stopPropagation(); | |||
| console.log("props exchange"); | |||
| props.acceptExchange(); | |||
| }; | |||
| @@ -139,8 +138,6 @@ const OfferCard = (props) => { | |||
| return true; | |||
| }, [userId, props.offer]); | |||
| console.log("props", props); | |||
| return ( | |||
| <React.Fragment> | |||
| <OfferCardContainer | |||
| @@ -185,6 +182,7 @@ const OfferCard = (props) => { | |||
| ) | |||
| : "" | |||
| } | |||
| alt={t("offer.imageAlt")} | |||
| vertical={props.vertical} | |||
| ></OfferImage> | |||
| </OfferImageContainer> | |||
| @@ -303,6 +301,7 @@ const OfferCard = (props) => { | |||
| <Tooltip title={t("messages.tooltip")}> | |||
| <TooltipInnerContainer> | |||
| <MessageIcon | |||
| aria-label={t("labels.messageUser")} | |||
| showMessageIcon={showMessageIcon} | |||
| vertical={props.vertical} | |||
| onClick={messageUser} | |||
| @@ -27,11 +27,8 @@ import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| const SkeletonOfferCard = (props) => { | |||
| const { isMobile } = useIsMobile(); | |||
| return ( | |||
| <SkeletonOfferCardsContainer> | |||
| <SkeletonOfferCardContainer | |||
| vertical={props.vertical} | |||
| skeleton={props.skeleton} | |||
| > | |||
| <SkeletonOfferCardsContainer skeleton={props.skeleton}> | |||
| <SkeletonOfferCardContainer vertical={props.vertical}> | |||
| <SkeletonTitleAboveImage vertical={props.vertical} /> | |||
| <LeftPart vertical={props.vertical}> | |||
| <SkeletonImage vertical={props.vertical} /> | |||
| @@ -73,5 +70,10 @@ SkeletonOfferCard.propTypes = { | |||
| vertical: PropTypes.bool, | |||
| aboveChat: PropTypes.bool, | |||
| }; | |||
| SkeletonOfferCard.defaultProps = { | |||
| skeleton: false, | |||
| vertical: false, | |||
| aboveChat: false, | |||
| }; | |||
| export default SkeletonOfferCard; | |||
| @@ -7,7 +7,7 @@ import { | |||
| } from "../../../Styles/globalStyleComponents"; | |||
| export const SkeletonOfferCardsContainer = styled(Box)` | |||
| display: flex; | |||
| display: ${props => props.skeleton ? "flex" : "none"}; | |||
| `; | |||
| export const SkeletonOfferCardContainer = styled(BackgroundTransition)` | |||
| @@ -29,13 +29,10 @@ const RequestExchangeCard = (props) => { | |||
| () => exchange?.buyer?.userId === userId, | |||
| [exchange, userId] | |||
| ); | |||
| console.log("requester", requester); | |||
| console.log("exchange: ", exchange); | |||
| const haveIAccepted = useMemo( | |||
| () => (amIBuyer ? exchange?.buyer?.accepted : exchange?.seller?.accepted), | |||
| [amIBuyer, exchange] | |||
| ); | |||
| console.log(haveIAccepted) | |||
| const interlucatorUserId = useMemo( | |||
| () => (amIBuyer ? exchange?.seller?.userId : exchange?.buyer?.userId), | |||
| [exchange, amIBuyer] | |||
| @@ -11,26 +11,29 @@ import { | |||
| ToggleContainer, | |||
| } from "./DropdownList.styled"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const DropdownList = (props) => { | |||
| const [listShown, setListShown] = useState(props.defaultOpen); | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| if (props.open !== null || props.open !== undefined) | |||
| setListShown(props.open); | |||
| }, [props.open]); | |||
| const handleShow = () => { | |||
| if (props.setIsOpened) | |||
| props.setIsOpened(!listShown); | |||
| if (!props.disabled) | |||
| setListShown((prevState) => !prevState); | |||
| if (props.setIsOpened) props.setIsOpened(!listShown); | |||
| if (!props.disabled) setListShown((prevState) => !prevState); | |||
| if (!(props.open !== null || props.open !== undefined)) | |||
| setListShown((prevState) => !prevState); | |||
| }; | |||
| return ( | |||
| <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | |||
| <DropdownHeader> | |||
| {props.dropdownIcon && ( | |||
| <DropdownIcon | |||
| aria-label={t("labels.dropdownIcon")} | |||
| onClick={!props.disabled ? () => handleShow() : () => {}} | |||
| disabled={props.disabled} | |||
| > | |||
| @@ -50,6 +53,7 @@ const DropdownList = (props) => { | |||
| : listShown | |||
| ) ? ( | |||
| <ToggleIconOpened | |||
| aria-label={t("labels.dropdownClose")} | |||
| style={props.toggleIconStyles} | |||
| onClick={!props.disabled ? () => handleShow() : () => {}} | |||
| > | |||
| @@ -57,6 +61,7 @@ const DropdownList = (props) => { | |||
| </ToggleIconOpened> | |||
| ) : ( | |||
| <ToggleIconClosed | |||
| aria-label={t("labels.dropdownOpen")} | |||
| style={props.toggleIconStyles} | |||
| onClick={!props.disabled ? () => handleShow() : () => {}} | |||
| disabled={props.disabled} | |||
| @@ -5,13 +5,13 @@ import { IconButton } from "../../../../Buttons/IconButton/IconButton"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { MailIcon } from "./MyMessagesButton.styled"; | |||
| import history from "../../../../../store/utils/history"; | |||
| import { CHAT_PAGE } from "../../../../../constants/pages"; | |||
| import { MESSAGES_LIST_PAGE } from "../../../../../constants/pages"; | |||
| const MyMessagesButton = (props) => { | |||
| const { t } = useTranslation(); | |||
| const handleClick = () => { | |||
| props.toggleDrawer(); | |||
| history.push(CHAT_PAGE); | |||
| history.push(MESSAGES_LIST_PAGE); | |||
| }; | |||
| return ( | |||
| <DrawerButton onClick={handleClick}> | |||
| @@ -9,9 +9,11 @@ import PopoverComponent from "../../Popovers/PopoverComponent"; | |||
| import { MyMessages } from "../../Popovers/MyMessages/MyMessages"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { useEffect } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const MyMessagesButton = () => { | |||
| const location = useLocation(); | |||
| const { t } = useTranslation(); | |||
| const [msgPopoverOpen, setMsgPopoverOpen] = useState(false); | |||
| const [msgAnchorEl, setMsgAnchorEl] = useState(null); | |||
| useEffect(() => { | |||
| @@ -29,6 +31,7 @@ const MyMessagesButton = () => { | |||
| return ( | |||
| <> | |||
| <IconButton | |||
| aria-label={t("header.myMessages")} | |||
| onClick={openMsgPopover} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| @@ -8,8 +8,10 @@ import { MyPosts } from "../../Popovers/MyPosts/MyPosts"; | |||
| import { useState } from "react"; | |||
| import { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const MySwapsButton = (props) => { | |||
| const {t} = useTranslation(); | |||
| const location = useLocation(); | |||
| const [postsPopoverOpen, setPostsPopoverOpen] = useState(false); | |||
| const [postsAnchorEl, setPostsAnchorEl] = useState(null); | |||
| @@ -33,6 +35,7 @@ const MySwapsButton = (props) => { | |||
| return ( | |||
| <> | |||
| <IconButton | |||
| aria-label={t("header.myOffers")} | |||
| onClick={openPostsPopover} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| @@ -25,8 +25,6 @@ const SearchInput = forwardRef((props, ref) => { | |||
| }; | |||
| const listener = useCallback( | |||
| (event) => { | |||
| console.log(event); | |||
| console.log(ref); | |||
| if (event.keyCode === 13) { | |||
| event.preventDefault(); | |||
| handleManualSearch(); | |||
| @@ -9,9 +9,11 @@ import { MyProfile } from "../../Popovers/MyProfile/MyProfile"; | |||
| import { useState } from "react"; | |||
| import { useEffect } from "react"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const UserButton = (props) => { | |||
| const location = useLocation(); | |||
| const {t} = useTranslation(); | |||
| const [userPopoverOpen, setUserPopoverOpen] = useState(false); | |||
| const [userAnchorEl, setUserAnchorEl] = useState(null); | |||
| @@ -32,6 +34,7 @@ const UserButton = (props) => { | |||
| <UserButtonContainer onClick={openUserPopover}> | |||
| <UserName>{props.name}</UserName> | |||
| <IconButton | |||
| aria-label={t("header.myProfile")} | |||
| style={{ | |||
| background: selectedTheme.colors.primaryIconBackgroundColor, | |||
| color: selectedTheme.colors.primaryPurple, | |||
| @@ -0,0 +1,54 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| GridButton, | |||
| GridButtonsContainer, | |||
| GridLineIcon, | |||
| GridSquareIcon, | |||
| } from "./GridButtons.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| const GridButtons = (props) => { | |||
| return ( | |||
| <GridButtonsContainer hideGrid={props?.hideGrid}> | |||
| {/* Setting display of offer cards to full width */} | |||
| <GridButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.iconStrokeColor | |||
| : selectedTheme.colors.primaryPurple | |||
| } | |||
| onClick={() => props?.setIsGrid(false)} | |||
| > | |||
| <GridLineIcon /> | |||
| </GridButton> | |||
| {/* ^^^^^^ */} | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <GridButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.primaryPurple | |||
| : selectedTheme.colors.iconStrokeColor | |||
| } | |||
| onClick={() => props?.setIsGrid(true)} | |||
| > | |||
| <GridSquareIcon /> | |||
| </GridButton> | |||
| {/* ^^^^^^ */} | |||
| </GridButtonsContainer> | |||
| ); | |||
| }; | |||
| GridButtons.propTypes = { | |||
| isGrid: PropTypes.bool, | |||
| setIsGrid: PropTypes.func, | |||
| hideGrid: PropTypes.bool, | |||
| }; | |||
| GridButtons.defaultProps = { | |||
| isGrid: false, | |||
| setIsGrid: () => {}, | |||
| hideGrid: false, | |||
| }; | |||
| export default GridButtons; | |||
| @@ -0,0 +1,20 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { IconButton } from "../../../Buttons/IconButton/IconButton"; | |||
| import { ReactComponent as GridSquare } from "../../../../assets/images/svg/offer-grid-square.svg"; | |||
| import { ReactComponent as GridLine } from "../../../../assets/images/svg/offer-grid-line.svg"; | |||
| export const GridButton = styled(IconButton)` | |||
| padding: 2px 10px; | |||
| @media (max-width: 1500px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const GridButtonsContainer = styled(Box)` | |||
| flex-direction: row; | |||
| display: ${(props) => (props.hideGrid ? "none" : "flex")}; | |||
| justify-content: space-between; | |||
| margin-right: 40px; | |||
| `; | |||
| export const GridSquareIcon = styled(GridSquare)``; | |||
| export const GridLineIcon = styled(GridLine)``; | |||
| @@ -1,61 +1,31 @@ | |||
| import React, { useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ButtonContainer, | |||
| CategoryHeaderIcon, | |||
| CategoryIcon, | |||
| HeaderAltLocation, | |||
| HeaderButton, | |||
| HeaderButtons, | |||
| HeaderCategoryString, | |||
| HeaderCompanyString, | |||
| HeaderContainer, | |||
| HeaderLocation, | |||
| HeaderLocationsMainString, | |||
| HeaderLocationsString, | |||
| HeaderOptions, | |||
| HeaderSelect, | |||
| HeaderSubcategoryString, | |||
| HeaderText, | |||
| HeaderTitleContainer, | |||
| HeaderTitleText, | |||
| HeaderWrapperContainer, | |||
| IconStyled, | |||
| LocationIcon, | |||
| PageTitleContainer, | |||
| SelectOption, | |||
| SubcategoryIcon, | |||
| SwapsHeaderIcon, | |||
| SwapsIcon, | |||
| SwapsTitle, | |||
| TooltipInnerContainer, | |||
| UserIcon, | |||
| } from "./Header.styled"; | |||
| import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg"; | |||
| import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-grid-line.svg"; | |||
| import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { sortEnum } from "../../../enums/sortEnum"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Tooltip } from "@mui/material"; | |||
| import SkeletonHeader from "./SkeletonHeader/SkeletonHeader"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectHeaderString } from "../../../store/selectors/filtersSelectors"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton"; | |||
| // import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton"; | |||
| import history from "../../../store/utils/history"; | |||
| const DownArrow = (props) => ( | |||
| <IconStyled {...props}> | |||
| <Down /> | |||
| </IconStyled> | |||
| ); | |||
| // import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton"; | |||
| import TooltipHeader from "./TooltipHeader/TooltipHeader"; | |||
| import GridButtons from "./GridButtons/GridButtons"; | |||
| import HeaderSelect from "./HeaderSelect/HeaderSelect"; | |||
| const Header = (props) => { | |||
| const { t } = useTranslation(); | |||
| const sorting = props?.sorting; | |||
| const headerString = useSelector(selectHeaderString); | |||
| const { isMobile } = useIsMobile(); | |||
| // Changing header string on refresh or on load | |||
| @@ -104,25 +74,11 @@ const Header = (props) => { | |||
| ); | |||
| }); | |||
| const handleChangeSelect = (event) => { | |||
| sorting?.changeSorting(event.target.value); | |||
| }; | |||
| const handleClickCategory = () => { | |||
| props?.offers?.filters?.locations.clear(); | |||
| props?.offers?.filters?.subcategory.clear(); | |||
| props?.offers?.applyFilters(); | |||
| }; | |||
| const handleClickSubcategory = () => { | |||
| props?.offers?.filters?.locations.clear(); | |||
| props?.offers?.applyFilters(); | |||
| }; | |||
| const goBack = () => { | |||
| history.goBack(); | |||
| }; | |||
| return ( | |||
| <> | |||
| <SkeletonHeader skeleton={props?.skeleton} myOffers={props?.myOffers} /> | |||
| {props?.skeleton && ( | |||
| <SkeletonHeader skeleton={props?.skeleton} myOffers={props?.myOffers} /> | |||
| )} | |||
| <HeaderWrapperContainer | |||
| className={props.className} | |||
| skeleton={props?.skeleton} | |||
| @@ -130,120 +86,28 @@ const Header = (props) => { | |||
| > | |||
| <HeaderContainer> | |||
| {/* Setting appropriate header title if page is market place or my offers */} | |||
| <Tooltip title={headerString.text}> | |||
| <TooltipInnerContainer> | |||
| {!props?.myOffers ? ( | |||
| <> | |||
| <CategoryHeaderIcon /> | |||
| <HeaderLocation> | |||
| {/* {headerString} */} | |||
| <HeaderCategoryString | |||
| component="span" | |||
| onClick={handleClickCategory} | |||
| > | |||
| {headerString.categoryString} | |||
| {/* {headerString.subcategoryString && <> </>} */} | |||
| </HeaderCategoryString> | |||
| <HeaderSubcategoryString | |||
| component="span" | |||
| onClick={handleClickSubcategory} | |||
| > | |||
| {headerString.subcategoryString} | |||
| {/* {headerString.locationsString && <> </>} */} | |||
| </HeaderSubcategoryString> | |||
| <HeaderLocationsString component="span"> | |||
| <HeaderLocationsMainString component="span"> | |||
| {headerString.locationsString} | |||
| </HeaderLocationsMainString> | |||
| <HeaderCompanyString> | |||
| {headerString.companiesString} | |||
| </HeaderCompanyString> | |||
| <HeaderAltLocation component="span"> | |||
| {altString} | |||
| </HeaderAltLocation> | |||
| </HeaderLocationsString> | |||
| </HeaderLocation> | |||
| </> | |||
| ) : ( | |||
| <> | |||
| {!isMobile ? ( | |||
| <HeaderTitleContainer> | |||
| {headerIcon} | |||
| <HeaderTitleText>{headerTitle}</HeaderTitleText> | |||
| </HeaderTitleContainer> | |||
| ) : ( | |||
| !props.hideBackButton && ( | |||
| <ButtonContainer onClick={goBack}> | |||
| <ArrowButton side={"left"}></ArrowButton> | |||
| <HeaderText>{t("messages.goBack")}</HeaderText> | |||
| </ButtonContainer> | |||
| ) | |||
| )} | |||
| </> | |||
| )} | |||
| </TooltipInnerContainer> | |||
| </Tooltip> | |||
| <TooltipHeader | |||
| altText={altString} | |||
| headerTitle={headerTitle} | |||
| headerIcon={headerIcon} | |||
| offers={props?.offers} | |||
| hideBackButton={props?.hideBackButton} | |||
| /> | |||
| {/* ^^^^^^ */} | |||
| <HeaderOptions> | |||
| {!props.hideGrid && ( | |||
| <HeaderButtons> | |||
| {/* Setting display of offer cards to full width */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.iconStrokeColor | |||
| : selectedTheme.colors.primaryPurple | |||
| } | |||
| onClick={() => props?.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props?.isGrid | |||
| ? selectedTheme.colors.primaryPurple | |||
| : selectedTheme.colors.iconStrokeColor | |||
| } | |||
| onClick={() => props?.setIsGrid(true)} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| </HeaderButtons> | |||
| )} | |||
| <GridButtons | |||
| hideGrid={props?.hideGrid} | |||
| isGrid={props?.isGrid} | |||
| setIsGrid={props?.setIsGrid} | |||
| /> | |||
| {/* Select option to choose sorting */} | |||
| {!props.hideSorting && ( | |||
| <HeaderSelect | |||
| value={ | |||
| sorting?.selectedSortOption?.value | |||
| ? sorting?.selectedSortOption | |||
| : "default" | |||
| } | |||
| IconComponent={DownArrow} | |||
| onChange={handleChangeSelect} | |||
| myOffers={props?.myOffers} | |||
| > | |||
| <SelectOption style={{ display: "none" }} value="default"> | |||
| {t("reviews.sortBy")} | |||
| </SelectOption> | |||
| {Object.keys(sorting?.sortOptions).map((property) => { | |||
| if (sorting?.sortOptions[property].value === 0) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sorting?.sortOptions[property]} | |||
| key={sorting?.sortOptions[property].value} | |||
| > | |||
| {sorting?.sortOptions[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelect> | |||
| )} | |||
| <HeaderSelect | |||
| myOffers={props?.myOffers} | |||
| sorting={sorting} | |||
| hideSorting={props?.hideSorting} | |||
| /> | |||
| {/* ^^^^^^ */} | |||
| </HeaderOptions> | |||
| </HeaderContainer> | |||
| @@ -1,11 +1,7 @@ | |||
| import { Box, Link, MenuItem, Typography } from "@mui/material"; | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import Option from "../../Select/Option/Option"; | |||
| import Select from "../../Select/Select"; | |||
| import { ReactComponent as Swaps } from "../../../assets/images/svg/swaps.svg"; | |||
| import { ReactComponent as CategoryHeader } from "../../../assets/images/svg/category-header.svg"; | |||
| import { ReactComponent as User } from "../../../assets/images/svg/user.svg"; | |||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | |||
| import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg"; | |||
| @@ -23,166 +19,20 @@ export const HeaderContainer = styled(Box)` | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| `; | |||
| export const TooltipInnerContainer = styled(Box)` | |||
| width: 100%; | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| & * { | |||
| display: inline; | |||
| } | |||
| `; | |||
| export const HeaderLocation = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| flex: 2; | |||
| margin-left: 9px; | |||
| max-width: 50%; | |||
| position: relative; | |||
| top: -2px; | |||
| &:after { | |||
| content: ${(props) => (props.initial ? `":"` : `""`)}; | |||
| @media (max-width: 600px) { | |||
| content: ""; | |||
| } | |||
| } | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderCategoryString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| cursor: pointer; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderSubcategoryString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| cursor: pointer; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderLocationsString = styled(Typography)` | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| max-width: 100%; | |||
| `; | |||
| export const HeaderLocationsMainString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| max-width: 200px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderCompanyString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| max-width: 200px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderButton = styled(IconButton)` | |||
| padding: 2px 10px; | |||
| @media (max-width: 1500px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const HeaderOptions = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex: 1; | |||
| justify-content: end; | |||
| `; | |||
| export const HeaderSelect = styled(Select)` | |||
| width: 210px; | |||
| height: 35px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| margin-top: 3px; | |||
| font-weight: 400; | |||
| position: relative; | |||
| left: -5px; | |||
| background-color: white; | |||
| & div:first-child { | |||
| padding-left: 8px; | |||
| } | |||
| @media (max-width: 650px) { | |||
| width: 144px; | |||
| height: 30px; | |||
| font-size: 14px; | |||
| top: 60px; | |||
| left: ${(props) => (props.myOffers ? "-7px" : "0")}; | |||
| } | |||
| `; | |||
| export const SelectItem = styled(MenuItem)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| `; | |||
| export const SelectOption = styled(Option)` | |||
| @media (max-width: 600px) { | |||
| height: 20px !important; | |||
| min-height: 35px; | |||
| margin: 2px; | |||
| } | |||
| `; | |||
| export const IconStyled = styled(Box)` | |||
| position: relative; | |||
| top: 0; | |||
| right: 10px; | |||
| `; | |||
| export const HeaderButtons = styled(Box)` | |||
| flex-direction: row; | |||
| display: flex; | |||
| justify-content: space-between; | |||
| margin-right: 40px; | |||
| `; | |||
| export const HeaderAltLocation = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| position: relative; | |||
| top: 0.5px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const RefreshIcon = styled(Swaps)` | |||
| width: 18px; | |||
| height: 18px; | |||
| @@ -200,15 +50,6 @@ export const MySwapsTitle = styled(Typography)` | |||
| position: relative; | |||
| left: 9px; | |||
| `; | |||
| export const CategoryHeaderIcon = styled(CategoryHeader)` | |||
| position: relative; | |||
| top: 4px; | |||
| @media (max-width: 600px) { | |||
| width: 12px; | |||
| height: 12px; | |||
| top: 1px; | |||
| } | |||
| `; | |||
| export const CategoryIcon = styled(Category)` | |||
| position: relative; | |||
| top: 4px; | |||
| @@ -243,42 +84,11 @@ export const SwapsTitle = styled(Typography)` | |||
| line-height: 16px; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| `; | |||
| export const ButtonContainer = styled(Link)` | |||
| width: fit-content; | |||
| cursor: pointer; | |||
| display: flex; | |||
| justify-content: start; | |||
| align-items: center; | |||
| gap: 12px; | |||
| text-decoration: none; | |||
| min-width: 200px; | |||
| `; | |||
| export const HeaderText = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| border-bottom: 1px dotted ${selectedTheme.colors.primaryPurple}; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| } | |||
| `; | |||
| export const SwapsHeaderIcon = styled(SwapsIcon)` | |||
| width: 18px; | |||
| height: 18px; | |||
| `; | |||
| export const HeaderTitleContainer = styled(Box)` | |||
| position: relative; | |||
| left: 5px; | |||
| `; | |||
| export const HeaderTitleText = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| font-size: 16px; | |||
| position: relative; | |||
| bottom: 2px; | |||
| left: 2px; | |||
| `; | |||
| export const UserIcon = styled(User)` | |||
| position: relative; | |||
| top: 3px; | |||
| @@ -0,0 +1,51 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { HeaderSelectContainer, SelectOption } from "./HeaderSelect.styled"; | |||
| const HeaderSelect = (props) => { | |||
| const sorting = props?.sorting; | |||
| const { t } = useTranslation(); | |||
| const handleChangeSelect = (event) => { | |||
| sorting?.changeSorting(event.target.value); | |||
| }; | |||
| return ( | |||
| <HeaderSelectContainer | |||
| hideSorting={props?.hideSorting} | |||
| value={ | |||
| sorting?.selectedSortOption?.value | |||
| ? sorting?.selectedSortOption | |||
| : "default" | |||
| } | |||
| onChange={handleChangeSelect} | |||
| myOffers={props?.myOffers} | |||
| > | |||
| <SelectOption style={{ display: "none" }} value="default"> | |||
| {t("reviews.sortBy")} | |||
| </SelectOption> | |||
| {Object.keys(sorting?.sortOptions).map((property) => { | |||
| if (sorting?.sortOptions[property].value === 0) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sorting?.sortOptions[property]} | |||
| key={sorting?.sortOptions[property].value} | |||
| > | |||
| {sorting?.sortOptions[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelectContainer> | |||
| ); | |||
| }; | |||
| HeaderSelect.propTypes = { | |||
| sorting: PropTypes.any, | |||
| myOffers: PropTypes.bool, | |||
| hideSorting: PropTypes.bool, | |||
| }; | |||
| HeaderSelect.defaultProps = { | |||
| myOffers: false, | |||
| hideSorting: false, | |||
| }; | |||
| export default HeaderSelect; | |||
| @@ -0,0 +1,38 @@ | |||
| import { MenuItem } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import Option from "../../../Select/Option/Option"; | |||
| import Select from "../../../Select/Select"; | |||
| export const HeaderSelectContainer = styled(Select)` | |||
| width: 210px; | |||
| height: 35px; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| margin-top: 3px; | |||
| font-weight: 400; | |||
| position: relative; | |||
| left: -5px; | |||
| background-color: white; | |||
| display: ${(props) => props.hideSorting && "none"}; | |||
| & div:first-child { | |||
| padding-left: 8px; | |||
| } | |||
| @media (max-width: 650px) { | |||
| width: 144px; | |||
| height: 30px; | |||
| font-size: 14px; | |||
| top: 60px; | |||
| left: ${(props) => (props.myOffers ? "-7px" : "0")}; | |||
| } | |||
| `; | |||
| export const SelectItem = styled(MenuItem)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| `; | |||
| export const SelectOption = styled(Option)` | |||
| @media (max-width: 600px) { | |||
| height: 20px !important; | |||
| min-height: 35px; | |||
| margin: 2px; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,113 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { Tooltip } from "@mui/material"; | |||
| import { | |||
| ButtonContainer, | |||
| CategoryHeaderIcon, | |||
| HeaderAltLocation, | |||
| HeaderCategoryString, | |||
| HeaderCompanyString, | |||
| HeaderLocation, | |||
| HeaderLocationsMainString, | |||
| HeaderLocationsString, | |||
| HeaderSubcategoryString, | |||
| HeaderText, | |||
| HeaderTitleContainer, | |||
| HeaderTitleText, | |||
| TooltipInnerContainer, | |||
| } from "./TooltipHeader.styled"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectHeaderString } from "../../../../store/selectors/filtersSelectors"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| import { ArrowButton } from "../../../Buttons/ArrowButton/ArrowButton"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import history from "../../../../store/utils/history"; | |||
| const TooltipHeader = (props) => { | |||
| const headerString = useSelector(selectHeaderString); | |||
| const { isMobile } = useIsMobile(); | |||
| const { t } = useTranslation(); | |||
| const handleClickCategory = () => { | |||
| props?.offers?.filters?.locations.clear(); | |||
| props?.offers?.filters?.subcategory.clear(); | |||
| props?.offers?.applyFilters(); | |||
| }; | |||
| const handleClickSubcategory = () => { | |||
| props?.offers?.filters?.locations.clear(); | |||
| props?.offers?.applyFilters(); | |||
| }; | |||
| const goBack = () => { | |||
| history.goBack(); | |||
| }; | |||
| return ( | |||
| <Tooltip title={headerString.text}> | |||
| <TooltipInnerContainer> | |||
| {!props?.myOffers ? ( | |||
| <> | |||
| <CategoryHeaderIcon /> | |||
| <HeaderLocation> | |||
| <HeaderCategoryString | |||
| component="span" | |||
| onClick={handleClickCategory} | |||
| > | |||
| {headerString.categoryString} | |||
| </HeaderCategoryString> | |||
| <HeaderSubcategoryString | |||
| component="span" | |||
| onClick={handleClickSubcategory} | |||
| > | |||
| {headerString.subcategoryString} | |||
| </HeaderSubcategoryString> | |||
| <HeaderLocationsString component="span"> | |||
| <HeaderLocationsMainString component="span"> | |||
| {headerString.locationsString} | |||
| </HeaderLocationsMainString> | |||
| <HeaderCompanyString> | |||
| {headerString.companiesString} | |||
| </HeaderCompanyString> | |||
| <HeaderAltLocation component="span"> | |||
| {props?.altText} | |||
| </HeaderAltLocation> | |||
| </HeaderLocationsString> | |||
| </HeaderLocation> | |||
| </> | |||
| ) : ( | |||
| <> | |||
| {!isMobile ? ( | |||
| <HeaderTitleContainer> | |||
| {props?.headerIcon} | |||
| <HeaderTitleText>{props?.headerTitle}</HeaderTitleText> | |||
| </HeaderTitleContainer> | |||
| ) : ( | |||
| !props.hideBackButton && ( | |||
| <ButtonContainer onClick={goBack}> | |||
| <ArrowButton side={"left"}></ArrowButton> | |||
| <HeaderText>{t("messages.goBack")}</HeaderText> | |||
| </ButtonContainer> | |||
| ) | |||
| )} | |||
| </> | |||
| )} | |||
| </TooltipInnerContainer> | |||
| </Tooltip> | |||
| ); | |||
| }; | |||
| TooltipHeader.propTypes = { | |||
| myOffers: PropTypes.bool, | |||
| altText: PropTypes.string, | |||
| headerIcon: PropTypes.node, | |||
| headerTitle: PropTypes.string, | |||
| hideBackButton: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| }; | |||
| TooltipHeader.defaultProps = { | |||
| myOffers: false, | |||
| altText: "", | |||
| headerIcon: <></>, | |||
| headerString: "", | |||
| hideBackButton: false, | |||
| }; | |||
| export default TooltipHeader; | |||
| @@ -0,0 +1,153 @@ | |||
| import { Box, Link, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { ReactComponent as CategoryHeader } from "../../../../assets/images/svg/category-header.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const TooltipInnerContainer = styled(Box)` | |||
| width: 100%; | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| & * { | |||
| display: inline; | |||
| } | |||
| `; | |||
| export const CategoryHeaderIcon = styled(CategoryHeader)` | |||
| position: relative; | |||
| top: 4px; | |||
| @media (max-width: 600px) { | |||
| width: 12px; | |||
| height: 12px; | |||
| top: 1px; | |||
| } | |||
| `; | |||
| export const HeaderLocation = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| flex: 2; | |||
| margin-left: 9px; | |||
| max-width: 50%; | |||
| position: relative; | |||
| top: -2px; | |||
| &:after { | |||
| content: ${(props) => (props.initial ? `":"` : `""`)}; | |||
| @media (max-width: 600px) { | |||
| content: ""; | |||
| } | |||
| } | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderCategoryString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| cursor: pointer; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderSubcategoryString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| cursor: pointer; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderLocationsString = styled(Typography)` | |||
| /* position: relative; | |||
| bottom: 2px; */ | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| max-width: 100%; | |||
| `; | |||
| export const HeaderLocationsMainString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| max-width: 200px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderCompanyString = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| max-width: 200px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| font-weight: 700; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| padding-top: 3px; | |||
| } | |||
| `; | |||
| export const HeaderAltLocation = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| position: relative; | |||
| top: 0.5px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const HeaderTitleContainer = styled(Box)` | |||
| position: relative; | |||
| left: 5px; | |||
| `; | |||
| export const HeaderTitleText = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| font-size: 16px; | |||
| position: relative; | |||
| bottom: 2px; | |||
| left: 2px; | |||
| `; | |||
| export const ButtonContainer = styled(Link)` | |||
| width: fit-content; | |||
| cursor: pointer; | |||
| display: flex; | |||
| justify-content: start; | |||
| align-items: center; | |||
| gap: 12px; | |||
| text-decoration: none; | |||
| min-width: 200px; | |||
| `; | |||
| export const HeaderText = styled(Typography)` | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.colors.primaryPurple}; | |||
| border-bottom: 1px dotted ${selectedTheme.colors.primaryPurple}; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| } | |||
| `; | |||
| @@ -1,102 +1,31 @@ | |||
| import React, { useRef } from "react"; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { FilterContainer, FilterIcon, OffersContainer } from "./Offers.styled"; | |||
| import OfferCard from "../../Cards/OfferCard/OfferCard"; | |||
| import { useSelector } from "react-redux"; | |||
| import Paging from "../../Paging/Paging"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import { startChat } from "../../../util/helpers/chatHelper"; | |||
| import OffersNotFound from "./OffersNotFound"; | |||
| // import HeadersMyOffers from "./SearchBar/SearchBar"; | |||
| import SkeletonOfferCard from "../../Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard"; | |||
| import BigProfileCard from "../../Cards/ProfileCard/BigProfileCard/BigProfileCard"; | |||
| import SearchField from "../../TextFields/SearchField/SearchField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import OffersList from "./OffersList/OffersList"; | |||
| import OffersFilterButton from "./OffersFilterButton/OffersFilterButton"; | |||
| import OffersSearchField from "./OffersSearchField/OffersSearchField"; | |||
| import OffersNotFound from "./OffersNotFound/OffersNotFound"; | |||
| const Offers = (props) => { | |||
| const chats = useSelector(selectLatestChats); | |||
| const offersRef = useRef(null); | |||
| const userId = useSelector(selectUserId); | |||
| const { t } = useTranslation(); | |||
| const offers = props?.offers; | |||
| // For skeleton screen | |||
| const arrayForMapping = Array.apply(null, Array(4)).map(() => {}); | |||
| const messageOneUser = (offer) => { | |||
| startChat(chats, offer, userId); | |||
| }; | |||
| const toggleFilters = () => { | |||
| props?.toggleFilters(); | |||
| }; | |||
| return ( | |||
| <> | |||
| {!props?.skeleton ? ( | |||
| <> | |||
| <FilterContainer | |||
| isAdmin={props?.isAdmin} | |||
| onClick={toggleFilters} | |||
| number={offers?.filters?.numOfFiltersChosen} | |||
| myOffers={props?.myOffers} | |||
| > | |||
| <FilterIcon /> | |||
| </FilterContainer> | |||
| {(props?.myOffers || props?.isAdmin) && ( | |||
| // <HeadersMyOffers | |||
| <SearchField | |||
| searchMyOffers={offers?.search?.searchOffers} | |||
| handleSearch={offers?.apply} | |||
| isAdmin={props?.isAdmin} | |||
| offers={offers} | |||
| isUsers={props.isUsers} | |||
| placeholder={ | |||
| props.isUsers | |||
| ? t("admin.users.searchPlaceholder") | |||
| : t("header.searchOffers") | |||
| } | |||
| /> | |||
| )} | |||
| {offers?.allOffersToShow?.length === 0 ? ( | |||
| <OffersNotFound /> | |||
| ) : ( | |||
| <OffersContainer ref={offersRef}> | |||
| {props.isUsers | |||
| ? props.users?.map((item) => ( | |||
| <BigProfileCard | |||
| key={item._id} | |||
| profile={item} | |||
| halfwidth={props?.isGrid} | |||
| /> | |||
| )) | |||
| : offers?.allOffersToShow?.map((item) => { | |||
| return ( | |||
| <OfferCard | |||
| key={item._id} | |||
| offer={item} | |||
| halfwidth={props?.isGrid} | |||
| messageUser={messageOneUser} | |||
| isMyOffer={item?.userId === userId || props?.isAdmin} | |||
| isAdmin={props?.isAdmin} | |||
| /> | |||
| ); | |||
| })} | |||
| <Paging | |||
| totalElements={offers?.totalOffers} | |||
| elementsPerPage={10} | |||
| current={parseInt(offers?.paging?.currentPage)} | |||
| changePage={offers?.paging?.changePage} | |||
| /> | |||
| </OffersContainer> | |||
| )} | |||
| </> | |||
| ) : ( | |||
| <> | |||
| {arrayForMapping.map((item, index) => ( | |||
| <SkeletonOfferCard key={index} skeleton /> | |||
| ))} | |||
| </> | |||
| )} | |||
| <OffersFilterButton /> | |||
| <OffersSearchField /> | |||
| <OffersNotFound /> | |||
| <OffersList | |||
| loading={props?.skeleton} | |||
| offers={offers} | |||
| isAdmin={props.isAdmin} | |||
| isGrid={props?.isGrid} | |||
| isUsers={props?.isUsers} | |||
| users={props?.users} | |||
| /> | |||
| {props?.skeleton && | |||
| arrayForMapping.map((item, index) => ( | |||
| <SkeletonOfferCard key={index} skeleton={props?.skeleton} /> | |||
| ))} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -106,7 +35,15 @@ Offers.propTypes = { | |||
| isGrid: PropTypes.bool, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| offers: PropTypes.shape({ | |||
| apply: PropTypes.func, | |||
| search: PropTypes.shape({ | |||
| searchOffers: PropTypes.func, | |||
| }), | |||
| filters: PropTypes.shape({ | |||
| numOfFiltersChosen: PropTypes.number, | |||
| }), | |||
| }), | |||
| toggleFilters: PropTypes.func, | |||
| isAdmin: PropTypes.bool, | |||
| isUsers: PropTypes.bool, | |||
| @@ -116,6 +53,20 @@ Offers.propTypes = { | |||
| Offers.defaultProps = { | |||
| myOffers: false, | |||
| users: [], | |||
| isAdmin: false, | |||
| isUsers: false, | |||
| isGrid: false, | |||
| skeleton: false, | |||
| toggleFilters: () => {}, | |||
| offers: { | |||
| apply: () => {}, | |||
| search: { | |||
| searchOffers: () => {}, | |||
| }, | |||
| filters: { | |||
| numOfFiltersChosen: 0, | |||
| }, | |||
| }, | |||
| }; | |||
| export default Offers; | |||
| @@ -1,39 +0,0 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import IconWithNumber from "../../Icon/IconWithNumber/IconWithNumber"; | |||
| import { ReactComponent as Filter } from "../../../assets/images/svg/filter.svg"; | |||
| export const OffersContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex-wrap: wrap; | |||
| justify-content: space-between; | |||
| margin-top: 5px; | |||
| position: relative; | |||
| padding-bottom: 60px; | |||
| `; | |||
| export const FilterContainer = styled(IconWithNumber)` | |||
| position: absolute; | |||
| top: 93px; | |||
| right: 24px; | |||
| cursor: pointer; | |||
| background-color: ${selectedTheme.colors.primaryBackgroundColor} !important; | |||
| & div { | |||
| width: 16px; | |||
| height: 16px; | |||
| background-color: ${selectedTheme.colors.primaryPurple}; | |||
| position: absolute; | |||
| top: -5px; | |||
| right: -5px; | |||
| line-height: 15px; | |||
| text-align: center; | |||
| } | |||
| @media (min-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const FilterIcon = styled(Filter)` | |||
| background-color: ${selectedTheme.colors.primaryBackgroundColor}; | |||
| `; | |||
| @@ -0,0 +1,43 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { FilterContainer, FilterIcon } from "./OffersFilterButton.styled"; | |||
| const OffersFilterButton = (props) => { | |||
| const toggleFilters = () => { | |||
| props?.toggleFilters(); | |||
| }; | |||
| return ( | |||
| <FilterContainer | |||
| isAdmin={props?.isAdmin} | |||
| onClick={toggleFilters} | |||
| number={props?.offers?.filters?.numOfFiltersChosen} | |||
| myOffers={props?.myOffers} | |||
| > | |||
| <FilterIcon /> | |||
| </FilterContainer> | |||
| ); | |||
| }; | |||
| OffersFilterButton.propTypes = { | |||
| isAdmin: PropTypes.bool, | |||
| offers: PropTypes.shape({ | |||
| filters: PropTypes.shape({ | |||
| numOfFiltersChosen: PropTypes.number, | |||
| }), | |||
| }), | |||
| myOffers: PropTypes.bool, | |||
| toggleFilters: PropTypes.func, | |||
| }; | |||
| OffersFilterButton.defaultProps = { | |||
| isAdmin: false, | |||
| offers: { | |||
| filters: { | |||
| numOfFiltersChosen: 0, | |||
| }, | |||
| }, | |||
| myOffers: false, | |||
| toggleFilters: () => {}, | |||
| }; | |||
| export default OffersFilterButton; | |||
| @@ -0,0 +1,28 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import IconWithNumber from "../../../Icon/IconWithNumber/IconWithNumber"; | |||
| import { ReactComponent as Filter } from "../../../../assets/images/svg/filter.svg"; | |||
| export const FilterContainer = styled(IconWithNumber)` | |||
| position: absolute; | |||
| top: 93px; | |||
| right: 24px; | |||
| cursor: pointer; | |||
| background-color: ${selectedTheme.colors.primaryBackgroundColor} !important; | |||
| & div { | |||
| width: 16px; | |||
| height: 16px; | |||
| background-color: ${selectedTheme.colors.primaryPurple}; | |||
| position: absolute; | |||
| top: -5px; | |||
| right: -5px; | |||
| line-height: 15px; | |||
| text-align: center; | |||
| } | |||
| @media (min-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const FilterIcon = styled(Filter)` | |||
| background-color: ${selectedTheme.colors.primaryBackgroundColor}; | |||
| `; | |||
| @@ -0,0 +1,73 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectTotalOffers } from "../../../../store/selectors/offersSelectors"; | |||
| import { OffersContainer } from "./OffersList.styled"; | |||
| import BigProfileCard from "../../../Cards/ProfileCard/BigProfileCard/BigProfileCard"; | |||
| import OfferCard from "../../../Cards/OfferCard/OfferCard"; | |||
| import Paging from "../../../Paging/Paging"; | |||
| import { startChat } from "../../../../util/helpers/chatHelper"; | |||
| import { selectLatestChats } from "../../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../../store/selectors/loginSelectors"; | |||
| const OffersList = (props) => { | |||
| const totalOffers = useSelector(selectTotalOffers); | |||
| const chats = useSelector(selectLatestChats); | |||
| const userId = useSelector(selectUserId); | |||
| const offers = props?.offers; | |||
| const messageOneUser = (offer) => { | |||
| startChat(chats, offer, userId); | |||
| }; | |||
| return ( | |||
| <OffersContainer show={!props?.loading && totalOffers !== 0}> | |||
| {props.isUsers | |||
| ? props.users?.map((item) => ( | |||
| <BigProfileCard | |||
| key={item._id} | |||
| profile={item} | |||
| halfwidth={props?.isGrid} | |||
| /> | |||
| )) | |||
| : offers?.allOffersToShow?.map((item) => { | |||
| return ( | |||
| <OfferCard | |||
| key={item._id} | |||
| offer={item} | |||
| halfwidth={props?.isGrid} | |||
| messageUser={messageOneUser} | |||
| isMyOffer={item?.userId === userId || props?.isAdmin} | |||
| isAdmin={props?.isAdmin} | |||
| /> | |||
| ); | |||
| })} | |||
| <Paging | |||
| totalElements={offers?.totalOffers} | |||
| elementsPerPage={10} | |||
| current={parseInt(offers?.paging?.currentPage)} | |||
| changePage={offers?.paging?.changePage} | |||
| /> | |||
| </OffersContainer> | |||
| ); | |||
| }; | |||
| OffersList.propTypes = { | |||
| offers: PropTypes.any, | |||
| isGrid: PropTypes.bool, | |||
| isUsers: PropTypes.bool, | |||
| users: PropTypes.array, | |||
| isAdmin: PropTypes.bool, | |||
| loading: PropTypes.bool, | |||
| }; | |||
| OffersList.defaultProps = { | |||
| isGrid: false, | |||
| isUsers: false, | |||
| users: [], | |||
| isAdmin: false, | |||
| offers: { | |||
| allOffersToShow: [], | |||
| }, | |||
| loading: false, | |||
| }; | |||
| export default OffersList; | |||
| @@ -0,0 +1,12 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const OffersContainer = styled(Box)` | |||
| display: ${props => props.show ? "flex" : "none"}; | |||
| flex-direction: row; | |||
| flex-wrap: wrap; | |||
| justify-content: space-between; | |||
| margin-top: 5px; | |||
| position: relative; | |||
| padding-bottom: 60px; | |||
| `; | |||
| @@ -1,55 +0,0 @@ | |||
| import React from "react"; | |||
| import { ReactComponent as LogoBroken } from "../../../assets/images/svg/logo-broken.svg"; | |||
| import { | |||
| Button, | |||
| OffersNotFoundContainer, | |||
| OffersNotFoundLogo, | |||
| OffersNotFoundDescription, | |||
| OffersNotFoundHeading, | |||
| } from "./OffersNotFound.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { Trans, useTranslation } from "react-i18next"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../../../constants/pages"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { fetchOffers } from "../../../store/actions/offers/offersActions"; | |||
| const OffersNotFound = () => { | |||
| const dispatch = useDispatch(); | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const showAllOffersHandler = () => { | |||
| dispatch(fetchOffers({ queryString: "" })); | |||
| history.replace({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| return ( | |||
| <OffersNotFoundContainer> | |||
| <OffersNotFoundLogo> | |||
| <LogoBroken /> | |||
| </OffersNotFoundLogo> | |||
| <OffersNotFoundHeading> | |||
| {t("offersNotFound.notFound")} | |||
| </OffersNotFoundHeading> | |||
| <OffersNotFoundDescription> | |||
| <Trans i18nKey="offersNotFound.errorMessage" /> | |||
| </OffersNotFoundDescription> | |||
| <Button | |||
| variant="contained" | |||
| buttoncolor={selectedTheme.colors.primaryYellow} | |||
| textcolor="black" | |||
| onClick={showAllOffersHandler} | |||
| > | |||
| {t("offersNotFound.showAllOffers")} | |||
| </Button> | |||
| </OffersNotFoundContainer> | |||
| ); | |||
| }; | |||
| export default OffersNotFound; | |||
| @@ -0,0 +1,67 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ReactComponent as LogoBroken } from "../../../../assets/images/svg/logo-broken.svg"; | |||
| import { | |||
| Button, | |||
| OffersNotFoundContainer, | |||
| OffersNotFoundLogo, | |||
| OffersNotFoundDescription, | |||
| OffersNotFoundHeading, | |||
| } from "./OffersNotFound.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { Trans, useTranslation } from "react-i18next"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../../../../constants/pages"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { fetchOffers } from "../../../../store/actions/offers/offersActions"; | |||
| import { selectTotalOffers } from "../../../../store/selectors/offersSelectors"; | |||
| const OffersNotFound = () => { | |||
| const dispatch = useDispatch(); | |||
| const history = useHistory(); | |||
| const totalOffers = useSelector(selectTotalOffers); | |||
| const { t } = useTranslation(); | |||
| const showAllOffersHandler = () => { | |||
| dispatch(fetchOffers({ queryString: "" })); | |||
| history.replace({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| return ( | |||
| <> | |||
| <OffersNotFoundContainer show={totalOffers === 0}> | |||
| <OffersNotFoundLogo> | |||
| <LogoBroken /> | |||
| </OffersNotFoundLogo> | |||
| <OffersNotFoundHeading> | |||
| {t("offersNotFound.notFound")} | |||
| </OffersNotFoundHeading> | |||
| <OffersNotFoundDescription> | |||
| <Trans i18nKey="offersNotFound.errorMessage" /> | |||
| </OffersNotFoundDescription> | |||
| <Button | |||
| variant="contained" | |||
| buttoncolor={selectedTheme.colors.primaryYellow} | |||
| textcolor="black" | |||
| onClick={showAllOffersHandler} | |||
| > | |||
| {t("offersNotFound.showAllOffers")} | |||
| </Button> | |||
| </OffersNotFoundContainer> | |||
| </> | |||
| ); | |||
| }; | |||
| OffersNotFound.propTypes = { | |||
| skeleton: PropTypes.bool, | |||
| }; | |||
| OffersNotFound.defaultProps = { | |||
| skeleton: false, | |||
| }; | |||
| export default OffersNotFound; | |||
| @@ -1,11 +1,11 @@ | |||
| import styled from "styled-components"; | |||
| import { Typography } from "@mui/material"; | |||
| import { Box } from "@mui/system"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| export const OffersNotFoundContainer = styled(Box)` | |||
| display: flex; | |||
| display: ${props => props.show ? "flex" : "none"}; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| @@ -0,0 +1,50 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import SearchField from "../../../TextFields/SearchField/SearchField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const OffersSearchField = (props) => { | |||
| const offers = props?.offers; | |||
| const { t } = useTranslation(); | |||
| if (props?.myOffers || props?.isAdmin) | |||
| return ( | |||
| <SearchField | |||
| searchMyOffers={offers?.search?.searchOffers} | |||
| handleSearch={offers?.apply} | |||
| isAdmin={props?.isAdmin} | |||
| offers={offers} | |||
| isUsers={props.isUsers} | |||
| placeholder={ | |||
| props.isUsers | |||
| ? t("admin.users.searchPlaceholder") | |||
| : t("header.searchOffers") | |||
| } | |||
| /> | |||
| ); | |||
| return <></>; | |||
| }; | |||
| OffersSearchField.propTypes = { | |||
| offers: PropTypes.shape({ | |||
| apply: PropTypes.func, | |||
| search: PropTypes.shape({ | |||
| searchOffers: PropTypes.func, | |||
| }), | |||
| }), | |||
| isUsers: PropTypes.bool, | |||
| isAdmin: PropTypes.bool, | |||
| myOffers: PropTypes.bool, | |||
| }; | |||
| OffersSearchField.defaultProps = { | |||
| offers: { | |||
| apply: () => {}, | |||
| search: { | |||
| searchOffers: () => {}, | |||
| }, | |||
| }, | |||
| isUsers: false, | |||
| isAdmin: false, | |||
| myOffers: false, | |||
| }; | |||
| export default OffersSearchField; | |||
| @@ -25,7 +25,6 @@ const Modal = () => { | |||
| } else { | |||
| document.body.style.overflow = "auto"; | |||
| } | |||
| console.log("MODALS: ", modals); | |||
| return ( | |||
| <> | |||
| {modals?.createOfferModal && <CreateOffer {...modals?.props} />} | |||
| @@ -7,8 +7,11 @@ import { | |||
| PagingContainer, | |||
| ThreeDots, | |||
| } from "./Paging.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const Paging = (props) => { | |||
| const {t} = useTranslation(); | |||
| // Determining total pages | |||
| const pages = props.pages | |||
| ? props.pages | |||
| @@ -27,6 +30,7 @@ const Paging = (props) => { | |||
| <PagingContainer className={props.className}> | |||
| {/* Left arrow */} | |||
| <Arrow | |||
| aria-label={t("labels.prevPage")} | |||
| onClick={() => props.changePage(props.current - 1)} | |||
| disabled={props.current - 1 < 1} | |||
| > | |||
| @@ -71,6 +75,7 @@ const Paging = (props) => { | |||
| {/* Right arrow */} | |||
| <Arrow | |||
| aria-label={t("labels.nextPage")} | |||
| onClick={() => props.changePage(props.current + 1)} | |||
| disabled={props.current + 1 > pages} | |||
| > | |||
| @@ -10,18 +10,18 @@ export const REGISTER_PAGE = "/register"; | |||
| export const REGISTER_SUCCESSFUL_PAGE = "/register/success"; | |||
| export const RESET_PASSWORD_PAGE = "/reset-password/:token"; | |||
| export const CREATE_OFFER_PAGE = "/create-offer"; | |||
| export const ITEM_DETAILS_PAGE = "/proizvodi/:idProizvod"; | |||
| export const PROFILE_PAGE = "/profile/:idProfile"; | |||
| export const CHAT_PAGE = "/messages"; | |||
| export const CHAT_MESSAGE_PAGE = "/messages/:idChat"; | |||
| export const ITEM_DETAILS_PAGE = "/offers/:offerId"; | |||
| export const PROFILE_PAGE = "/profiles/:profileId"; | |||
| export const MESSAGES_LIST_PAGE = "/messages"; | |||
| export const DIRECT_CHAT_PAGE = "/messages/:chatId"; | |||
| export const MY_OFFERS_PAGE = "/myoffers"; | |||
| export const ABOUT_PAGE = "/about"; | |||
| export const PRICES_PAGE = "/prices"; | |||
| export const POLICY_PRIVACY_PAGE = "/policy"; | |||
| export const ADMIN_HOME_PAGE = "/admin"; | |||
| export const ADMIN_USERS_PAGE = "/admin/users"; | |||
| export const ADMIN_SINGLE_USER_PAGE = "/admin/users/:idProfile"; | |||
| export const ADMIN_ITEM_DETAILS_PAGE = "/admin/proizvodi/:idProizvod"; | |||
| export const ADMIN_SINGLE_USER_PAGE = "/admin/profiles/:profileId"; | |||
| export const ADMIN_ITEM_DETAILS_PAGE = "/admin/offers/:offerId"; | |||
| export const ADMIN_CATEGORIES_PAGE = "/admin/categories"; | |||
| export const ADMIN_LOCATIONS_PAGE = "/admin/locations"; | |||
| export const ADMIN_PAYMENT_PAGE = "/admin/payment"; | |||
| @@ -195,6 +195,7 @@ export default { | |||
| checkButtonLabel: "Pogledaj proizvod", | |||
| offers: "Objave", | |||
| tooltip: "Izmeni objavu", | |||
| imageAlt: "Offer image", | |||
| }, | |||
| apiErrors: { | |||
| somethingWentWrong: "Greška sa serverom!", | |||
| @@ -256,12 +257,13 @@ export default { | |||
| tooltip: "Pošalji poruku", | |||
| requestSent: "Uspešno ste ponudili trampu kompaniji.", | |||
| requestAccepted: "Kompanija je prihvatila trampu sa Vama.", | |||
| requestReceived: "Da li želite da prihvatite trampu sa nama za gorenavedeni proizvod?", | |||
| requestReceived: | |||
| "Da li želite da prihvatite trampu sa nama za gorenavedeni proizvod?", | |||
| acceptRequest: "Prihvati", | |||
| acceptedRequest: "Prihvaćeno", | |||
| declineRequest: "Odbij", | |||
| requestSuccessfulLong: "Uspešno ste ostvarili trampu sa ovom kompanijom.", | |||
| requestSentLong: "Ponudili ste trampu kompaniji. Čeka se odgovor..." | |||
| requestSentLong: "Ponudili ste trampu kompaniji. Čeka se odgovor...", | |||
| }, | |||
| editProfile: { | |||
| website: "Web Sajt", | |||
| @@ -590,4 +592,12 @@ export default { | |||
| confirm: "Obriši komentar", | |||
| }, | |||
| }, | |||
| labels: { | |||
| dropdownIcon: "Dropdown icon", | |||
| dropdownOpen: "Open dropdown", | |||
| dropdownClose: "Close dropdown", | |||
| messageUser: "Message user", | |||
| prevPage: "Previous page", | |||
| nextPage: "Next page", | |||
| }, | |||
| }; | |||
| @@ -1,3 +1,6 @@ | |||
| .ToastBody * { | |||
| font-family: "DM Sans"; | |||
| } | |||
| * { | |||
| font-display: swap; | |||
| } | |||
| @@ -18,7 +18,7 @@ ReactDOM.render( | |||
| <Provider store={store}> | |||
| <ColorModeProvider> | |||
| <PersistGate loading={null} persistor={persistor}> | |||
| <GlobalStyle/> | |||
| <GlobalStyle /> | |||
| <App /> | |||
| </PersistGate> | |||
| </ColorModeProvider> | |||
| @@ -4,32 +4,34 @@ import MiniChatColumn from "../../components/DirectChat/MiniChatColumn/MiniChatC | |||
| import ChatGridLayout from "../../layouts/ChatGridLayout/ChatGridLayout"; | |||
| import { useSwipeable } from "react-swipeable"; | |||
| import { | |||
| ChatMessagesPageContainer, | |||
| DirectChatPageContainer, | |||
| SwiperContainer, | |||
| } from "./ChatMessages.styled"; | |||
| } from "./DirectChatPage.styled"; | |||
| import { useHistory } from "react-router-dom"; | |||
| export const ChatMessagesPage = () => { | |||
| export const DirectChatPage = () => { | |||
| const history = useHistory(); | |||
| const goBack = () => { | |||
| history.goBack(); | |||
| }; | |||
| const handlersBox = useSwipeable({ | |||
| onSwipedRight: () => setTimeout(goBack, 0), | |||
| trackMouse: false, | |||
| // preventDefaultTouchmoveEvent: true | |||
| }); | |||
| return ( | |||
| <SwiperContainer {...handlersBox}> | |||
| <ChatMessagesPageContainer> | |||
| <DirectChatPageContainer> | |||
| <ChatGridLayout | |||
| content={<DirectChat />} | |||
| leftCard={<MiniChatColumn />} | |||
| /> | |||
| </ChatMessagesPageContainer> | |||
| </DirectChatPageContainer> | |||
| </SwiperContainer> | |||
| ); | |||
| }; | |||
| ChatMessagesPage.propTypes = {}; | |||
| DirectChatPage.propTypes = {}; | |||
| export default ChatMessagesPage; | |||
| export default DirectChatPage; | |||
| @@ -2,7 +2,7 @@ import { Container } from "@mui/system"; | |||
| import styled from "styled-components"; | |||
| import { transitionOnLoadFromRight } from "../../components/Styles/globalStyleComponents"; | |||
| export const ChatMessagesPageContainer = styled(Container)` | |||
| export const DirectChatPageContainer = styled(Container)` | |||
| max-width: none; | |||
| @media (max-width: 600px) { | |||
| animation: 0.2s ease 0s 1 ${transitionOnLoadFromRight}; | |||
| @@ -1,15 +1,43 @@ | |||
| import React from 'react'; | |||
| import React, { useState } from "react"; | |||
| import { HomePageContainer } from "./HomePage.styled"; | |||
| import FilterCard from "../../components/Cards/FilterCard/FilterCard"; | |||
| import MainLayout from "../../layouts/MainLayout/MainLayout"; | |||
| import MarketPlace from "../../components/MarketPlace/MarketPlace"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { useSelector } from "react-redux"; | |||
| import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| import useOffers from "../../hooks/useOffers/useOffers"; | |||
| const HomePage = () => { | |||
| const isLoadingOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_SCOPE) | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const offers = useOffers(); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| return ( | |||
| <div className="c-error-page"> | |||
| <div className="c-error-page__content"> | |||
| <h1 className="c-error-page__title">Home page</h1> | |||
| </div> | |||
| </div> | |||
| <HomePageContainer> | |||
| <MainLayout | |||
| leftCard={ | |||
| <FilterCard | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| </HomePageContainer> | |||
| ); | |||
| }; | |||
| HomePage.propTypes = {}; | |||
| export default HomePage; | |||
| @@ -1,43 +0,0 @@ | |||
| import React, { useState } from "react"; | |||
| import { HomePageContainer } from "./HomePage.styled"; | |||
| import FilterCard from "../../components/Cards/FilterCard/FilterCard"; | |||
| import MainLayout from "../../layouts/MainLayout/MainLayout"; | |||
| import MarketPlace from "../../components/MarketPlace/MarketPlace"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { useSelector } from "react-redux"; | |||
| import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| import useOffers from "../../hooks/useOffers/useOffers"; | |||
| const HomePage = () => { | |||
| const isLoadingOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_SCOPE) | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const offers = useOffers(); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| return ( | |||
| <HomePageContainer> | |||
| <MainLayout | |||
| leftCard={ | |||
| <FilterCard | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| </HomePageContainer> | |||
| ); | |||
| }; | |||
| export default HomePage; | |||
| @@ -1,29 +1,29 @@ | |||
| import React from "react"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { ChatColumn } from "../../components/ChatColumn/ChatColumn"; | |||
| import { CHAT_MESSAGE_PAGE } from "../../constants/pages"; | |||
| import { DIRECT_CHAT_PAGE } from "../../constants/pages"; | |||
| import ChatLayout from "../../layouts/ChatLayout/ChatLayout"; | |||
| import { replaceInRoute } from "../../util/helpers/routeHelpers"; | |||
| import { ChatPageContainer } from "./Chat.styled"; | |||
| import { MessagesListPageContainer } from "./MessagesListPage.styled"; | |||
| export const ChatPage = () => { | |||
| export const MessagesListPage = () => { | |||
| const history = useHistory(); | |||
| const navigateToChat = (chatId) => { | |||
| setTimeout(() => { | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { | |||
| replaceInRoute(DIRECT_CHAT_PAGE, { | |||
| idChat: chatId, | |||
| }) | |||
| ); | |||
| }, 120); | |||
| }; | |||
| return ( | |||
| <ChatPageContainer> | |||
| <MessagesListPageContainer> | |||
| <ChatLayout content={<ChatColumn navigateToChat={navigateToChat} />} /> | |||
| </ChatPageContainer> | |||
| </MessagesListPageContainer> | |||
| ); | |||
| }; | |||
| ChatPage.propTypes = {}; | |||
| MessagesListPage.propTypes = {}; | |||
| export default ChatPage; | |||
| export default MessagesListPage; | |||
| @@ -2,7 +2,7 @@ import { Container } from "@mui/system"; | |||
| import styled from "styled-components"; | |||
| import { transitionOnLoadFromLeft } from "../../components/Styles/globalStyleComponents"; | |||
| export const ChatPageContainer = styled(Container)` | |||
| export const MessagesListPageContainer = styled(Container)` | |||
| padding: 0; | |||
| margin: 0; | |||
| animation: 0.2s ease 0s 1 ${transitionOnLoadFromLeft}; | |||
| @@ -10,7 +10,6 @@ import { | |||
| function* fetchCategories() { | |||
| try { | |||
| const data = yield call(attemptFetchCategories); | |||
| console.log("ASDFASDF", data); | |||
| if (!data?.data) throw new Error(); | |||
| yield put(setCategories(data.data)); | |||
| yield put(fetchCategoriesSuccess()); | |||
| @@ -1,4 +1,4 @@ | |||
| import { CHAT_MESSAGE_PAGE } from "../../constants/pages"; | |||
| import { DIRECT_CHAT_PAGE } from "../../constants/pages"; | |||
| import history from "../../store/utils/history"; | |||
| import { replaceInRoute } from "./routeHelpers"; | |||
| @@ -8,12 +8,12 @@ export const messageUserHelper = (chats, userId, offer) => { | |||
| ); | |||
| if (chatItem !== undefined) { | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { idChat: chatItem.chat._id }) | |||
| replaceInRoute(DIRECT_CHAT_PAGE, { idChat: chatItem.chat._id }) | |||
| ); | |||
| } else { | |||
| if (offer?.offer?.userId !== userId) { | |||
| history.push( | |||
| replaceInRoute(CHAT_MESSAGE_PAGE, { idChat: "newMessage" }), | |||
| replaceInRoute(DIRECT_CHAT_PAGE, { idChat: "newMessage" }), | |||
| { | |||
| offerId: offer?.offer?._id, | |||
| } | |||
| @@ -2,7 +2,6 @@ import { reviewEnum } from "../../enums/reviewEnum"; | |||
| export const sortReviews = (reviews = [], positive = false) => { | |||
| let newReviews; | |||
| console.log(reviews); | |||
| if (!Array.isArray(reviews)) return []; | |||
| if (positive) { | |||
| newReviews = [ | |||