| @@ -14544,6 +14544,11 @@ | |||
| "react-transition-group": "^4.3.0" | |||
| } | |||
| }, | |||
| "react-singleton-hook": { | |||
| "version": "3.4.0", | |||
| "resolved": "https://registry.npmjs.org/react-singleton-hook/-/react-singleton-hook-3.4.0.tgz", | |||
| "integrity": "sha512-eQEpyacGAaRejmWUizUdNNQFn5AO0iaKRSl1jxgC0FQadVY/I1WFuPrYiutglPzO9s8yEbIh95UXVJQel4d7HQ==" | |||
| }, | |||
| "react-toastify": { | |||
| "version": "9.0.3", | |||
| "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.3.tgz", | |||
| @@ -34,6 +34,7 @@ | |||
| "react-router-dom": "^5.2.0", | |||
| "react-scripts": "4.0.3", | |||
| "react-select": "^4.3.1", | |||
| "react-singleton-hook": "^3.4.0", | |||
| "react-toastify": "^9.0.3", | |||
| "redux": "^4.1.0", | |||
| "redux-persist": "^6.0.0", | |||
| @@ -83,14 +83,14 @@ const App = () => { | |||
| <Header /> | |||
| <GlobalStyle /> | |||
| <ToastContainer /> | |||
| {/* <div> | |||
| {/* <div> | |||
| <p>Connected: {"" + isConnected}</p> | |||
| <br /> | |||
| <p>Last pong: {lastPong || "-"}</p> | |||
| <br /> | |||
| <button onClick={sendPing}>Send ping</button> | |||
| </div> */} | |||
| <AppRoutes /> | |||
| <AppRoutes /> | |||
| </StyledEngineProvider> | |||
| </Router> | |||
| ); | |||
| @@ -3,6 +3,7 @@ body { | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-osx-font-smoothing: grayscale; | |||
| overflow-anchor: none; | |||
| background-color: #F1F1F1; | |||
| } | |||
| * { | |||
| @@ -13,27 +13,27 @@ const CategoryChoser = (props) => { | |||
| const filters = props.filters; | |||
| const { t } = useTranslation(); | |||
| const handleSelectCategory = (category) => { | |||
| filters.setSelectedCategory(category); | |||
| filters.clearSelectedSubcategory(); | |||
| filters.category.setSelectedCategory(category); | |||
| filters.subcategory.setSelectedSubcategory({}); | |||
| }; | |||
| return ( | |||
| <FilterRadioDropdown | |||
| data={[...filters?.categories]} | |||
| data={[...filters?.category.allCategories]} | |||
| icon={ | |||
| filters.selectedCategory?.name ? ( | |||
| filters.category.selectedCategoryLocally?.name ? ( | |||
| <CategoryChosenIcon /> | |||
| ) : ( | |||
| <CategoryIcon /> | |||
| ) | |||
| } | |||
| title={ | |||
| filters.selectedCategory?.name | |||
| ? filters.selectedCategory?.name | |||
| filters.category.selectedCategoryLocally?.name | |||
| ? filters.category.selectedCategoryLocally?.name | |||
| : t("filters.categories.title") | |||
| } | |||
| searchPlaceholder={t("filters.categories.placeholder")} | |||
| setSelected={handleSelectCategory} | |||
| selected={filters.selectedCategory} | |||
| selected={filters.category.selectedCategoryLocally} | |||
| firstOption={firstCategoryOption} | |||
| /> | |||
| ); | |||
| @@ -10,15 +10,11 @@ const LocationChoser = (props) => { | |||
| return ( | |||
| <FilterCheckboxDropdown | |||
| searchPlaceholder={t("filters.location.placeholder")} | |||
| data={[...filters.locations]} | |||
| filters={ | |||
| filters?.selectedLocations?.length > 0 | |||
| ? [...filters.selectedLocations] | |||
| : [] | |||
| } | |||
| data={[...filters.locations.allLocations]} | |||
| filters={[...filters.locations.selectedLocationsLocally]} | |||
| icon={<LocationIcon />} | |||
| title={t("filters.location.title")} | |||
| setItemsSelected={filters.setSelectedLocations} | |||
| setItemsSelected={filters.locations.setSelectedLocations} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -2,56 +2,56 @@ import React, { useEffect, useMemo, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { SubcategoryIcon } from "./SubcategoryChoser.styled"; | |||
| import FilterRadioDropdown from "../../FilterDropdown/Radio/FilterRadioDropdown"; | |||
| import _ from "lodash"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const firstSubcategoryOption = { | |||
| label: "SVE PODKATEGORIJE", | |||
| value: { _id: 0 }, | |||
| }; | |||
| // const firstSubcategoryOption = { | |||
| // label: "SVE PODKATEGORIJE", | |||
| // value: { _id: 0 }, | |||
| // }; | |||
| const SubcategoryChoser = (props) => { | |||
| const filters = props.filters; | |||
| const { t } = useTranslation(); | |||
| const [isOpened, setIsOpened] = useState(false); | |||
| const [isDisabled, setIsDisabled] = useState(true); | |||
| const setInitialOpen = useMemo(() => { | |||
| return _.once(() => { | |||
| setIsOpened(true); | |||
| }); | |||
| }, []); | |||
| const subcategories = useMemo(() => { | |||
| return filters.category.getSubcategories( | |||
| filters.category.selectedCategoryLocally?.name | |||
| ); | |||
| }, [filters.category.selectedCategoryLocally]); | |||
| useEffect(() => { | |||
| if (!filters.selectedCategory || filters.selectedCategory?._id === 0) { | |||
| if (!filters.category.selectedCategoryLocally || filters.category.selectedCategoryLocally?._id === 0) { | |||
| setIsOpened(false); | |||
| setIsDisabled(true); | |||
| } else { | |||
| setIsDisabled(false); | |||
| setInitialOpen(); | |||
| setIsOpened(true); | |||
| } | |||
| }, [filters.selectedCategory]); | |||
| }, [filters.category.selectedCategoryLocally]); | |||
| const handleOpen = () => { | |||
| setIsOpened((prevState) => !prevState); | |||
| }; | |||
| console.log(filters); | |||
| return ( | |||
| <FilterRadioDropdown | |||
| data={filters.subcategories ? [...filters.subcategories] : []} | |||
| data={subcategories} | |||
| icon={<SubcategoryIcon />} | |||
| title={ | |||
| filters.selectedSubcategory?.name | |||
| ? filters.selectedSubcategory?.name | |||
| filters.subcategory.selectedSubcategory?.name | |||
| ? filters.subcategory.selectedSubcategory?.name | |||
| : t("filters.subcategories.title") | |||
| } | |||
| searchPlaceholder={t("filters.subcategories.placeholder")} | |||
| setSelected={filters.setSelectedSubcategory} | |||
| selected={filters.selectedSubcategory} | |||
| setSelected={filters.subcategory.setSelectedSubcategory} | |||
| selected={filters.subcategory.selectedSubcategoryLocally} | |||
| open={isOpened} | |||
| disabled={isDisabled} | |||
| handleOpen={handleOpen} | |||
| firstOption={firstSubcategoryOption} | |||
| firstOption={filters.subcategory.initialOption} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -1,7 +1,6 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ContentContainer, FilterCardContainer } from "./FilterCard.styled"; | |||
| import useFilters from "../../../hooks/useFilters"; | |||
| import HeaderBack from "../../ItemDetails/Header/Header"; | |||
| import FilterHeader from "./FilterHeader/FilterHeader"; | |||
| import FilterFooter from "./FilterFooter/FilterFooter"; | |||
| @@ -11,11 +10,11 @@ import LocationChoser from "./Choser/LocationChoser/LocationChoser"; | |||
| import SkeletonFilterCard from "./Skeleton/SkeletonFilterCard"; | |||
| const FilterCard = (props) => { | |||
| const filters = useFilters(props.myOffers); | |||
| const offers = props.offers; | |||
| const filters = offers.filters; | |||
| return ( | |||
| <FilterCardContainer | |||
| responsiveOpen={props.responsiveOpen} | |||
| responsive={props.responsive} | |||
| filtersOpened={props.filtersOpened} | |||
| myOffers={props.myOffers} | |||
| skeleton={props.skeleton} | |||
| > | |||
| @@ -38,9 +37,8 @@ const FilterCard = (props) => { | |||
| </ContentContainer> | |||
| <FilterFooter | |||
| closeResponsive={props.closeResponsive} | |||
| responsiveOpen={props.responsiveOpen} | |||
| filters={filters} | |||
| toggleFilters={props.toggleFilters} | |||
| filters={offers} | |||
| /> | |||
| </FilterCardContainer> | |||
| ); | |||
| @@ -48,13 +46,15 @@ const FilterCard = (props) => { | |||
| FilterCard.propTypes = { | |||
| children: PropTypes.node, | |||
| filters: PropTypes.any, | |||
| offers: PropTypes.any, | |||
| responsive: PropTypes.bool, | |||
| responsiveOpen: PropTypes.bool, | |||
| closeResponsive: PropTypes.func, | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| filtersOpened: PropTypes.bool, | |||
| toggleFilters: PropTypes.func, | |||
| }; | |||
| FilterCard.defaultProps = { | |||
| @@ -33,7 +33,7 @@ export const FilterCardContainer = styled(Box)` | |||
| @media (max-width: 900px) { | |||
| margin-left: -400px; | |||
| ${(props) => | |||
| props.responsiveOpen | |||
| props.filtersOpened | |||
| ? ` | |||
| display: "flex"; | |||
| margin-left: 0; | |||
| @@ -64,6 +64,7 @@ const FilterCheckboxDropdown = (props) => { | |||
| searchPlaceholder={props.searchPlaceholder} | |||
| isOpened={isOpened} | |||
| setIsOpened={setIsOpened} | |||
| setItemsSelected={props.setItemsSelected} | |||
| > | |||
| {dataToShow.map((item) => { | |||
| return ( | |||
| @@ -51,12 +51,11 @@ const FilterRadioDropdown = (props) => { | |||
| setIsOpened((prevState) => !prevState); | |||
| if (props.handleOpen) props.handleOpen(); | |||
| }; | |||
| return ( | |||
| <DropdownList | |||
| title={props.title} | |||
| textcolor={ | |||
| !props.selected || props.selected?._id === 0 | |||
| !props.selected || props.selected?._id === 0 || !props.selected?._id | |||
| ? selectedTheme.primaryText | |||
| : selectedTheme.primaryPurple | |||
| } | |||
| @@ -64,7 +63,7 @@ const FilterRadioDropdown = (props) => { | |||
| toggleIconClosed={<DropdownDown />} | |||
| toggleIconOpened={<DropdownUp />} | |||
| fullWidth | |||
| open={ props?.open !== undefined ? props.open : isOpened} | |||
| open={props?.open !== undefined ? props.open : isOpened} | |||
| disabled={props.disabled} | |||
| setIsOpened={handleOpen} | |||
| toggleIconStyles={{ | |||
| @@ -4,21 +4,23 @@ import { FilterFooterContainer } from "./FilterFooter.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import useScreenDimensions from "../../../../hooks/useScreenDimensions"; | |||
| const FilterFooter = (props) => { | |||
| const { t } = useTranslation(); | |||
| const filters = props.filters; | |||
| const screenDimensions = useScreenDimensions(); | |||
| const handleFilters = () => { | |||
| filters.applyFilters(); | |||
| if (props.closeResponsive) props.closeResponsive(); | |||
| filters.apply(); | |||
| if (props.toggleFilters) props.toggleFilters(); | |||
| }; | |||
| return ( | |||
| <FilterFooterContainer responsiveOpen={props.responsiveOpen}> | |||
| {props.responsiveOpen && ( | |||
| <FilterFooterContainer responsiveOpen={screenDimensions.width < 600}> | |||
| {screenDimensions.width < 600 && ( | |||
| <PrimaryButton | |||
| variant="outlined" | |||
| fullWidth | |||
| onClick={props.closeResponsive} | |||
| onClick={props.toggleFilters} | |||
| textcolor={selectedTheme.primaryPurple} | |||
| font="Open Sans" | |||
| style={{ | |||
| @@ -51,7 +53,7 @@ const FilterFooter = (props) => { | |||
| (FilterFooter.propTypes = { | |||
| responsiveOpen: PropTypes.bool, | |||
| closeResponsive: PropTypes.func, | |||
| toggleFilters: PropTypes.func, | |||
| filters: PropTypes.any, | |||
| }), | |||
| (FilterFooter.defaultProps = { | |||
| @@ -8,7 +8,7 @@ const FilterHeader = (props) => { | |||
| const filters = props.filters; | |||
| const { t } = useTranslation(); | |||
| const clearFilters = () => { | |||
| filters.clearFilters(); | |||
| filters.clear(); | |||
| }; | |||
| return ( | |||
| <FilterHeaderContainer> | |||
| @@ -10,7 +10,6 @@ import { | |||
| TitleSortContainer, | |||
| } from "./ChatColumn.styled"; | |||
| import { sortEnum } from "../../enums/sortEnum"; | |||
| import useSorting from "../../hooks/useSorting"; | |||
| import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg"; | |||
| import { IconStyled } from "../Icon/Icon.styled"; | |||
| import { Grid } from "@mui/material"; | |||
| @@ -20,6 +19,7 @@ import { useTranslation } from "react-i18next"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLatestChats } from "../../store/selectors/chatSelectors"; | |||
| import { fetchChats } from "../../store/actions/chat/chatActions"; | |||
| import useSorting from "../../hooks/useOffers/useSorting"; | |||
| export const DownArrow = (props) => { | |||
| <IconStyled {...props}> | |||
| @@ -3,8 +3,8 @@ import { | |||
| AddOfferButton, | |||
| AuthButtonsContainer, | |||
| EndIcon, | |||
| FilterContainer, | |||
| FilterIcon, | |||
| // FilterContainer, | |||
| // FilterIcon, | |||
| HeaderContainer, | |||
| LoginButton, | |||
| LogoContainer, | |||
| @@ -36,10 +36,10 @@ import { useTranslation } from "react-i18next"; | |||
| import { IconButton } from "../Buttons/IconButton/IconButton"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { useSearch } from "../../hooks/useSearch"; | |||
| import { selectProfileName } from "../../store/selectors/profileSelectors"; | |||
| import { useHistory, useRouteMatch } from "react-router-dom"; | |||
| import { | |||
| BASE_PAGE, | |||
| FORGOT_PASSWORD_MAIL_SENT, | |||
| FORGOT_PASSWORD_PAGE, | |||
| HOME_PAGE, | |||
| @@ -48,32 +48,28 @@ import { | |||
| REGISTER_SUCCESSFUL_PAGE, | |||
| RESET_PASSWORD_PAGE, | |||
| } from "../../constants/pages"; | |||
| import useFilters from "../../hooks/useFilters"; | |||
| import FilterCard from "../Cards/FilterCard/FilterCard"; | |||
| import { useQueryString } from "../../hooks/useQueryString"; | |||
| import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers"; | |||
| // import { convertQueryStringForFrontend } from "../../util/helpers/queryHelpers"; | |||
| import { fetchMineProfile } from "../../store/actions/profile/profileActions"; | |||
| import CreateOffer from "../Cards/CreateOfferCard/CreateOffer"; | |||
| import { Drawer as HeaderDrawer } from "./Drawer/Drawer"; | |||
| import useSearch from "../../hooks/useOffers/useSearch"; | |||
| // import useQueryString from "../../hooks/useOffers/useQueryString"; | |||
| const Header = () => { | |||
| const [openFilters, setOpenFilters] = useState(false); | |||
| // const setOpenFilters = useState(false)[1]; | |||
| const [showSearchBar, setShowSearchBar] = useState(true); | |||
| const [numberOfFilters, setNumberOfFilters] = useState(0); | |||
| const [showCreateOfferModal, setShowCreateOfferModal] = useState(false); | |||
| const { t } = useTranslation(); | |||
| const theme = useTheme(); | |||
| const searchRef = useRef(null); | |||
| const matches = useMediaQuery(theme.breakpoints.down("md")); | |||
| const user = useSelector(selectUserId); | |||
| const search = useSearch(); | |||
| const search = useSearch(() => {}); | |||
| const dispatch = useDispatch(); | |||
| const name = useSelector(selectProfileName); | |||
| const history = useHistory(); | |||
| const routeMatch = useRouteMatch(); | |||
| const filters = useFilters(); | |||
| const searchMobileRef = useRef(null); | |||
| const queryStringHook = useQueryString(); | |||
| const [openDrawer, setOpenDrawer] = useState(false); | |||
| useEffect(() => { | |||
| @@ -87,6 +83,9 @@ const Header = () => { | |||
| setUserAnchorEl(null); | |||
| }; | |||
| }, []); | |||
| useEffect(() => { | |||
| searchRef.current.value = search.searchString ?? ""; | |||
| }, [search.searchString]); | |||
| useEffect(() => { | |||
| if (history.location.pathname !== "/home") { | |||
| setShowSearchBar(false); | |||
| @@ -94,25 +93,18 @@ const Header = () => { | |||
| setShowSearchBar(true); | |||
| } | |||
| }, [history.location.pathname]); | |||
| useEffect(() => { | |||
| setNumberOfFilters(filters.calculateFiltersChosen()); | |||
| }, [ | |||
| filters.selectedCategory, | |||
| filters.selectedLocations, | |||
| filters.selectedSubcategory, | |||
| ]); | |||
| useEffect(() => { | |||
| if (queryStringHook.loadedFromURL) { | |||
| const queryObject = new URLSearchParams( | |||
| convertQueryStringFrontend(queryStringHook.queryString) | |||
| ); | |||
| if (queryObject.has("search")) { | |||
| searchRef.current.value = queryObject.get("search"); | |||
| searchMobileRef.current.value = queryObject.get("search"); | |||
| } | |||
| } | |||
| }); | |||
| // useEffect(() => { | |||
| // if (queryStringHook.loadedFromURL) { | |||
| // const queryObject = new URLSearchParams( | |||
| // convertQueryStringForFrontend(queryStringHook.queryString) | |||
| // ); | |||
| // if (queryObject.has("search")) { | |||
| // searchRef.current.value = queryObject.get("search"); | |||
| // searchMobileRef.current.value = queryObject.get("search"); | |||
| // } | |||
| // } | |||
| // }); | |||
| const closeCreateOfferModal = () => { | |||
| setShowCreateOfferModal(false); | |||
| @@ -143,8 +135,7 @@ const Header = () => { | |||
| location.pathname === REGISTER_SUCCESSFUL_PAGE || | |||
| location.pathname === FORGOT_PASSWORD_PAGE || | |||
| location.pathname === FORGOT_PASSWORD_MAIL_SENT || | |||
| location.pathname === RESET_PASSWORD_PAGE || | |||
| location.pathname === "/" | |||
| location.pathname === RESET_PASSWORD_PAGE | |||
| ) { | |||
| shouldShowHeader = false; | |||
| } | |||
| @@ -182,11 +173,22 @@ const Header = () => { | |||
| searchRef.current.removeEventListener("keyup", listener); | |||
| }; | |||
| const handleSearch = (value) => { | |||
| search.searchOffers(value); | |||
| }; | |||
| const toggleFilters = () => { | |||
| setOpenFilters((prevState) => !prevState); | |||
| if ( | |||
| history.location.pathname !== HOME_PAGE && | |||
| history.location.pathname !== BASE_PAGE | |||
| ) { | |||
| const newQueryString = new URLSearchParams({ search: value }); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: newQueryString.toString(), | |||
| }); | |||
| } else { | |||
| search.searchOffers(value); | |||
| } | |||
| }; | |||
| // const toggleFilters = () => { | |||
| // setOpenFilters((prevState) => !prevState); | |||
| // }; | |||
| const handleToggleDrawer = () => { | |||
| setOpenDrawer(!openDrawer); | |||
| @@ -382,30 +384,25 @@ const Header = () => { | |||
| fullWidth | |||
| shouldShow={showSearchBar} | |||
| ref={searchMobileRef} | |||
| placeholder={t("header.searchOffers")} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <React.Fragment> | |||
| <FilterContainer number={numberOfFilters}> | |||
| <FilterIcon onClick={toggleFilters} /> | |||
| </FilterContainer> | |||
| <EndIcon size="36px"> | |||
| <SearchIcon | |||
| onClick={() => handleSearch(searchMobileRef.current.value)} | |||
| /> | |||
| </EndIcon> | |||
| </React.Fragment> | |||
| <EndIcon size="36px"> | |||
| <SearchIcon | |||
| onClick={() => handleSearch(searchMobileRef.current.value)} | |||
| /> | |||
| </EndIcon> | |||
| ), | |||
| }} | |||
| placeholder={t("header.searchOffers")} | |||
| italicPlaceholder | |||
| onFocus={handleFocusSearch} | |||
| onBlur={handleBlurSearch} | |||
| /> | |||
| <FilterCard | |||
| {/* <FilterCard | |||
| responsive={true} | |||
| responsiveOpen={openFilters} | |||
| closeResponsive={toggleFilters} | |||
| /> | |||
| /> */} | |||
| {showCreateOfferModal && ( | |||
| <CreateOffer closeCreateOfferModal={closeCreateOfferModal} /> | |||
| )} | |||
| @@ -3,10 +3,8 @@ import styled from "styled-components"; | |||
| import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { TextField } from "../TextFields/TextField/TextField"; | |||
| import { ReactComponent as Search } from "../../assets/images/svg/magnifying-glass.svg"; | |||
| import { ReactComponent as Filter } from "../../assets/images/svg/filter.svg"; | |||
| import selectedTheme from "../../themes"; | |||
| import { Icon } from "../Icon/Icon"; | |||
| import IconWithNumber from "../Icon/IconWithNumber/IconWithNumber"; | |||
| export const SearchInput = styled(TextField)` | |||
| background-color: #f4f4f4; | |||
| @@ -195,25 +193,4 @@ export const SearchInputMobile = styled(SearchInput)` | |||
| width: 0; | |||
| } | |||
| `; | |||
| export const FilterContainer = styled(IconWithNumber)` | |||
| position: relative; | |||
| top: 8px; | |||
| left: 95px; | |||
| cursor: pointer; | |||
| background-color: ${selectedTheme.offerBackgroundColor} !important; | |||
| & div { | |||
| width: 16px; | |||
| height: 16px; | |||
| background-color: ${selectedTheme.primaryPurple}; | |||
| position: absolute; | |||
| top: -5px; | |||
| right: -5px; | |||
| line-height: 15px; | |||
| text-align: center; | |||
| padding-right: 2px; | |||
| } | |||
| `; | |||
| export const FilterIcon = styled(Filter)` | |||
| background-color: ${selectedTheme.offerBackgroundColor}; | |||
| `; | |||
| export const HeaderContainer = styled(Box)``; | |||
| @@ -1,20 +1,24 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { IconWithNumberContainer, Number } from './IconWithNumber.styled' | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { IconWithNumberContainer, Number } from "./IconWithNumber.styled"; | |||
| const IconWithNumber = (props) => { | |||
| return ( | |||
| <IconWithNumberContainer className={props.className}> | |||
| {props.children} | |||
| {props.number > 0 && <Number>{props.number}</Number>} | |||
| <IconWithNumberContainer | |||
| className={props.className} | |||
| onClick={props.onClick} | |||
| > | |||
| {props.children} | |||
| {props.number > 0 && <Number>{props.number}</Number>} | |||
| </IconWithNumberContainer> | |||
| ) | |||
| } | |||
| ); | |||
| }; | |||
| IconWithNumber.propTypes = { | |||
| children: PropTypes.node, | |||
| number: PropTypes.number, | |||
| className: PropTypes.string, | |||
| } | |||
| children: PropTypes.node, | |||
| number: PropTypes.number, | |||
| className: PropTypes.string, | |||
| onClick: PropTypes.func, | |||
| }; | |||
| export default IconWithNumber | |||
| export default IconWithNumber; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useEffect, useState } from "react"; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| HeaderAltLocation, | |||
| @@ -18,16 +18,11 @@ import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-gri | |||
| import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { sortEnum } from "../../../enums/sortEnum"; | |||
| import useFilters from "../../../hooks/useFilters"; | |||
| import useSorting from "../../../hooks/useSorting"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Tooltip } from "@mui/material"; | |||
| import { | |||
| ALL_CATEGORIES, | |||
| COMMA, | |||
| SPREAD, | |||
| } from "../../../constants/marketplaceHeaderTitle"; | |||
| import SkeletonHeader from "./SkeletonHeader/SkeletonHeader"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectHeaderString } from "../../../store/selectors/filtersSelectors"; | |||
| const DownArrow = (props) => ( | |||
| <IconStyled {...props}> | |||
| @@ -36,139 +31,107 @@ const DownArrow = (props) => ( | |||
| ); | |||
| const Header = (props) => { | |||
| const filters = useFilters(); | |||
| const sorting = useSorting(); | |||
| const { t } = useTranslation(); | |||
| const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | |||
| const [headerString, setHeaderString] = useState(ALL_CATEGORIES); | |||
| //Changing shown sort option on select menu | |||
| useEffect(() => { | |||
| setSortOption(sorting.selectedSortOption); | |||
| }, [sorting.selectedSortOption]); | |||
| const sorting = props.sorting; | |||
| const headerString = useSelector(selectHeaderString); | |||
| // Changing header string on refresh or on load | |||
| useEffect(() => { | |||
| let headerStringLocal = ALL_CATEGORIES; | |||
| if (filters.isApplied) { | |||
| // Adding category to header string | |||
| if (filters.selectedCategory?.name) { | |||
| headerStringLocal = filters.selectedCategory.name; | |||
| // Adding subcategories to header string | |||
| if (filters.selectedSubcategory?.name) { | |||
| headerStringLocal += `${SPREAD}${filters.selectedSubcategory.name}`; | |||
| } | |||
| } | |||
| // Adding locations to header string | |||
| if (filters.selectedLocations && filters.selectedLocations?.length > 0) { | |||
| headerStringLocal += SPREAD; | |||
| filters.selectedLocations.forEach((location, index) => { | |||
| // Checking if item is last | |||
| if (index + 1 === filters.selectedLocations.length) { | |||
| headerStringLocal += location.city; | |||
| } else { | |||
| headerStringLocal += location.city + COMMA; | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| setHeaderString(headerStringLocal); | |||
| }, [ | |||
| filters.isApplied, | |||
| filters.selectedCategory, | |||
| filters.selectedSubcategory, | |||
| filters.selectedLocations, | |||
| ]); | |||
| const handleChangeSelect = (event) => { | |||
| let chosenOption; | |||
| for (const sortOption in sortEnum) { | |||
| if (sortEnum[sortOption].value === event.target.value) { | |||
| chosenOption = sortEnum[sortOption]; | |||
| sorting.changeSorting(chosenOption); | |||
| } | |||
| } | |||
| // let chosenOption; | |||
| sorting.changeSorting(event.target.value); | |||
| // for (const sortOption in sortEnum) { | |||
| // if (sortEnum[sortOption].value === event.target.value) { | |||
| // chosenOption = sortEnum[sortOption]; | |||
| // sorting.changeSorting(chosenOption); | |||
| // } | |||
| // } | |||
| }; | |||
| return ( | |||
| <> | |||
| <SkeletonHeader skeleton={props.skeleton} animationStage={props.animationStage} /> | |||
| <HeaderContainer skeleton={props.skeleton}> | |||
| {/* Setting appropriate header title if page is market place or my offers */} | |||
| <Tooltip title={headerString}> | |||
| {!props.myOffers ? ( | |||
| headerString === "Sve kategorije" && | |||
| (sorting.selectedSortOption === sortEnum.INITIAL || | |||
| sorting.selectedSortOption === sortEnum.NEW) ? ( | |||
| <React.Fragment> | |||
| <HeaderLocation initial>{headerString}</HeaderLocation> | |||
| <HeaderAltLocation>{t("header.newOffers")}</HeaderAltLocation> | |||
| </React.Fragment> | |||
| <SkeletonHeader | |||
| skeleton={props.skeleton} | |||
| animationStage={props.animationStage} | |||
| /> | |||
| <HeaderContainer skeleton={props.skeleton}> | |||
| {/* Setting appropriate header title if page is market place or my offers */} | |||
| <Tooltip title={headerString}> | |||
| {!props.myOffers ? ( | |||
| headerString === "Sve kategorije" && | |||
| (sorting.selectedSortOption === sortEnum.INITIAL || | |||
| sorting.selectedSortOption === sortEnum.NEW) ? ( | |||
| <React.Fragment> | |||
| <HeaderLocation initial>{headerString}</HeaderLocation> | |||
| <HeaderAltLocation>{t("header.newOffers")}</HeaderAltLocation> | |||
| </React.Fragment> | |||
| ) : ( | |||
| <HeaderLocation>{headerString}</HeaderLocation> | |||
| ) | |||
| ) : ( | |||
| <HeaderLocation>{headerString}</HeaderLocation> | |||
| ) | |||
| ) : ( | |||
| <MySwapsTitle> | |||
| <RefreshIcon /> {t("header.myOffers")} | |||
| </MySwapsTitle> | |||
| )} | |||
| </Tooltip> | |||
| {/* ^^^^^^ */} | |||
| <MySwapsTitle> | |||
| <RefreshIcon /> {t("header.myOffers")} | |||
| </MySwapsTitle> | |||
| )} | |||
| </Tooltip> | |||
| {/* ^^^^^^ */} | |||
| <HeaderOptions> | |||
| <HeaderButtons> | |||
| {/* Setting display of offer cards to full width */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.iconStrokeColor | |||
| : selectedTheme.primaryPurple | |||
| } | |||
| onClick={() => props.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| <HeaderOptions> | |||
| <HeaderButtons> | |||
| {/* Setting display of offer cards to full width */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.iconStrokeColor | |||
| : selectedTheme.primaryPurple | |||
| } | |||
| onClick={() => props.setIsGrid(false)} | |||
| > | |||
| <GridLine /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.iconStrokeColor | |||
| } | |||
| onClick={() => props.setIsGrid(true)} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| {/* ^^^^^^ */} | |||
| </HeaderButtons> | |||
| {/* Setting display of offer cards to half width (Grid) */} | |||
| <HeaderButton | |||
| iconColor={ | |||
| props.isGrid | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.iconStrokeColor | |||
| {/* Select option to choose sorting */} | |||
| <HeaderSelect | |||
| value={ | |||
| sorting.selectedSortOption?.value | |||
| ? sorting.selectedSortOption | |||
| : "default" | |||
| } | |||
| onClick={() => props.setIsGrid(true)} | |||
| IconComponent={DownArrow} | |||
| onChange={handleChangeSelect} | |||
| > | |||
| <GridSquare /> | |||
| </HeaderButton> | |||
| <SelectOption style={{ display: "none" }} value="default"> | |||
| Sortiraj po | |||
| </SelectOption> | |||
| {Object.keys(sortEnum).map((property) => { | |||
| if (sortEnum[property].value === 0) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sortEnum[property]} | |||
| key={sortEnum[property].value} | |||
| > | |||
| {sortEnum[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelect> | |||
| {/* ^^^^^^ */} | |||
| </HeaderButtons> | |||
| {/* Select option to choose sorting */} | |||
| <HeaderSelect | |||
| value={sortOption?.value ? sortOption.value : "default"} | |||
| IconComponent={DownArrow} | |||
| onChange={handleChangeSelect} | |||
| > | |||
| <SelectOption style={{ display: "none" }} value="default"> | |||
| Sortiraj po | |||
| </SelectOption> | |||
| {Object.keys(sortEnum).map((property) => { | |||
| if(sortEnum[property].value === 0) return; | |||
| return ( | |||
| <SelectOption | |||
| value={sortEnum[property].value} | |||
| key={sortEnum[property].value} | |||
| > | |||
| {sortEnum[property].mainText} | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </HeaderSelect> | |||
| {/* ^^^^^^ */} | |||
| </HeaderOptions> | |||
| </HeaderContainer> | |||
| </HeaderOptions> | |||
| </HeaderContainer> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -182,6 +145,7 @@ Header.propTypes = { | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| sorting: PropTypes.any, | |||
| }; | |||
| Header.defaultProps = { | |||
| isGrid: false, | |||
| @@ -6,11 +6,26 @@ import Offers from "./Offers/Offers"; | |||
| const MarketPlace = (props) => { | |||
| const [isGrid, setIsGrid] = useState(false); | |||
| const offers = props.offers; | |||
| return ( | |||
| <MarketPlaceContainer> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} myOffers={props.myOffers} skeleton={props.skeleton} animationStage={props.animationStage}/> | |||
| <Offers isGrid={isGrid} myOffers={props.myOffers} animationStage={props.animationStage} skeleton={props.skeleton} /> | |||
| <Header | |||
| isGrid={isGrid} | |||
| setIsGrid={setIsGrid} | |||
| 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} | |||
| /> | |||
| </MarketPlaceContainer> | |||
| ); | |||
| }; | |||
| @@ -20,6 +35,8 @@ MarketPlace.propTypes = { | |||
| myOffers: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| skeleton: PropTypes.bool, | |||
| offers: PropTypes.any, | |||
| toggleFilters: PropTypes.func | |||
| }; | |||
| export default MarketPlace; | |||
| @@ -1,7 +1,6 @@ | |||
| import React, { useCallback, useRef } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { EndIcon, SearchIcon } from "./HeadersMyOffers.styled"; | |||
| import { EndIcon, SearchIcon, SearchInput } from "./HeadersMyOffers.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const HeadersMyOffers = (props) => { | |||
| @@ -21,9 +20,10 @@ const HeadersMyOffers = (props) => { | |||
| }; | |||
| const handleSearch = () => { | |||
| props.searchMyOffers(searchRef.current.value); | |||
| props.handleSearch(); | |||
| }; | |||
| return ( | |||
| <TextField | |||
| <SearchInput | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| @@ -43,6 +43,7 @@ const HeadersMyOffers = (props) => { | |||
| HeadersMyOffers.propTypes = { | |||
| children: PropTypes.node, | |||
| searchMyOffers: PropTypes.func, | |||
| handleSearch: PropTypes.func, | |||
| }; | |||
| export default HeadersMyOffers; | |||
| @@ -3,6 +3,7 @@ import styled from "styled-components"; | |||
| import { Icon } from "../../../Icon/Icon"; | |||
| import { ReactComponent as Search } from "../../../../assets/images/svg/magnifying-glass.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| export const HeadersMyOffersContainer = styled(Box)``; | |||
| @@ -23,3 +24,10 @@ export const SearchIcon = styled(Search)` | |||
| left: 11px; | |||
| } | |||
| `; | |||
| export const SearchInput = styled(TextField)` | |||
| width: 90%; | |||
| height: 36px; | |||
| & div { | |||
| height: 40px; | |||
| } | |||
| ` | |||
| @@ -1,13 +1,12 @@ | |||
| import React, { useRef } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { OffersContainer } from "./Offers.styled"; | |||
| 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 useOffers from "../../../hooks/useOffers"; | |||
| import OffersNotFound from "./OffersNotFound"; | |||
| import HeadersMyOffers from "./HeaderMyOffers.js/HeadersMyOffers"; | |||
| import SkeletonOfferCard from "../../Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard"; | |||
| @@ -16,21 +15,32 @@ const Offers = (props) => { | |||
| const chats = useSelector(selectLatestChats); | |||
| const offersRef = useRef(null); | |||
| const userId = useSelector(selectUserId); | |||
| const offers = useOffers(props.myOffers); | |||
| const arrayForMapping = Array.apply(null, Array(4)).map( | |||
| () => {} | |||
| ); | |||
| const offers = props.offers; | |||
| const arrayForMapping = Array.apply(null, Array(4)).map(() => {}); | |||
| const messageOneUser = (offer) => { | |||
| startChat(chats, offer, userId); | |||
| }; | |||
| const toggleFilters = () => { | |||
| props.toggleFilters(); | |||
| }; | |||
| return ( | |||
| <> | |||
| <FilterContainer | |||
| onClick={toggleFilters} | |||
| number={offers.filters.numOfFiltersChosen} | |||
| myOffers={props.myOffers} | |||
| > | |||
| <FilterIcon /> | |||
| </FilterContainer> | |||
| {!props.skeleton ? ( | |||
| <> | |||
| {props.myOffers && ( | |||
| <HeadersMyOffers searchMyOffers={offers.searchMyOffers} /> | |||
| <HeadersMyOffers | |||
| searchMyOffers={offers.search.searchOffers} | |||
| handleSearch={offers.apply} | |||
| /> | |||
| )} | |||
| {offers.allOffersToShow.length === 0 ? ( | |||
| <OffersNotFound /> | |||
| @@ -49,17 +59,21 @@ const Offers = (props) => { | |||
| <Paging | |||
| totalElements={offers.totalOffers} | |||
| elementsPerPage={10} | |||
| current={offers.page} | |||
| changePage={offers.handleDifferentPage} | |||
| current={parseInt(offers.paging.currentPage)} | |||
| changePage={offers.paging.changePage} | |||
| /> | |||
| </OffersContainer> | |||
| )} | |||
| </> | |||
| ) : ( | |||
| <> | |||
| {arrayForMapping.map((item, index)=> ( | |||
| <SkeletonOfferCard key={index} skeleton animationStage={props.animationStage} /> | |||
| ))} | |||
| {arrayForMapping.map((item, index) => ( | |||
| <SkeletonOfferCard | |||
| key={index} | |||
| skeleton | |||
| animationStage={props.animationStage} | |||
| /> | |||
| ))} | |||
| </> | |||
| )} | |||
| </> | |||
| @@ -72,6 +86,8 @@ Offers.propTypes = { | |||
| myOffers: PropTypes.bool, | |||
| skeleton: PropTypes.bool, | |||
| animationStage: PropTypes.number, | |||
| offers: PropTypes.any, | |||
| toggleFilters: PropTypes.func, | |||
| }; | |||
| Offers.defaultProps = { | |||
| @@ -1,5 +1,9 @@ | |||
| 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; | |||
| @@ -10,3 +14,26 @@ export const OffersContainer = styled(Box)` | |||
| position: relative; | |||
| padding-bottom: 60px; | |||
| `; | |||
| export const FilterContainer = styled(IconWithNumber)` | |||
| position: absolute; | |||
| top: ${props => props.myOffers ? "126px" : "93px"}; | |||
| right: 18px; | |||
| cursor: pointer; | |||
| background-color: ${selectedTheme.offerBackgroundColor} !important; | |||
| & div { | |||
| width: 16px; | |||
| height: 16px; | |||
| background-color: ${selectedTheme.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.offerBackgroundColor}; | |||
| `; | |||
| @@ -17,6 +17,7 @@ const Paging = (props) => { | |||
| : 1; | |||
| let moving = 0; | |||
| console.log(props.current) | |||
| // Making array of pages which contains 2 pages before and after current page | |||
| const pagesAsArray = Array.apply(null, Array(5)).map(() => {}); | |||
| @@ -6,6 +6,9 @@ export const KEY_SORTBY = "sortBy"; | |||
| export const KEY_SORT_DATE = "_des_date"; | |||
| export const KEY_SORT_POPULAR = "_des_popular"; | |||
| export const KEY_LOCATION = "location" | |||
| export const KEY_NAME = "name"; | |||
| export const KEY_SEARCH = "search" | |||
| export const VALUE_SORTBY_NEW = "newest"; | |||
| export const VALUE_SORTBY_OLD = "oldest"; | |||
| export const VALUE_SORTBY_POPULAR = "popular"; | |||
| export const initialSize = "10"; | |||
| @@ -1,182 +0,0 @@ | |||
| import _ from "lodash"; | |||
| import { useCallback, useEffect, useState } from "react"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { useSelector } from "react-redux"; | |||
| import { fetchCategories } from "../store/actions/categories/categoriesActions"; | |||
| import { | |||
| setFilteredCategory, | |||
| setFilteredLocations, | |||
| setFilteredSubcategory, | |||
| // setIsAppliedStatus, | |||
| } from "../store/actions/filters/filtersActions"; | |||
| import { fetchLocations } from "../store/actions/locations/locationsActions"; | |||
| import { | |||
| selectCategories, | |||
| selectSubcategories, | |||
| } from "../store/selectors/categoriesSelectors"; | |||
| import { | |||
| selectAppliedStatus, | |||
| selectSelectedCategory, | |||
| selectSelectedLocations, | |||
| selectSelectedSubcategory, | |||
| } from "../store/selectors/filtersSelectors"; | |||
| import { selectLocations } from "../store/selectors/locationsSelectors"; | |||
| import { useQueryString } from "./useQueryString"; | |||
| const useFilters = (myOffers) => { | |||
| const selectedCategory = useSelector(selectSelectedCategory); | |||
| const selectedSubcategory = useSelector(selectSelectedSubcategory); | |||
| const selectedLocations = useSelector(selectSelectedLocations); | |||
| const [loadedFromQS, setLoadedFromQS] = useState(false); | |||
| const isApplied = useSelector(selectAppliedStatus); | |||
| const categories = useSelector(selectCategories); | |||
| const subcategories = useSelector( | |||
| selectSubcategories(selectedCategory?.name) | |||
| ); | |||
| const locations = useSelector(selectLocations); | |||
| const dispatch = useDispatch(); | |||
| const queryStringHook = useQueryString(); | |||
| const fetchCategoriesAndLocations = useCallback( | |||
| _.once(() => { | |||
| dispatch(fetchCategories()); | |||
| dispatch(fetchLocations()); | |||
| }), | |||
| [] | |||
| ); | |||
| useEffect(() => { | |||
| fetchCategoriesAndLocations(); | |||
| }, []); | |||
| useEffect(() => { | |||
| const queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (categories?.length > 0 && locations?.length > 0) { | |||
| let category; | |||
| if (queryObject.has("category")) { | |||
| category = categories.find( | |||
| (item) => item.name === queryObject.get("category").toString() | |||
| ); | |||
| setSelectedCategory(category); | |||
| } else { | |||
| if (!myOffers) { | |||
| setSelectedCategory(); | |||
| } | |||
| } | |||
| if (queryObject.has("subcategory")) { | |||
| setSelectedSubcategory( | |||
| category?.subcategories?.find( | |||
| (item) => | |||
| item.name.toString() === queryObject.get("subcategory").toString() | |||
| ) | |||
| ); | |||
| } else { | |||
| if (!myOffers) { | |||
| setSelectedSubcategory(); | |||
| } | |||
| } | |||
| } | |||
| if (queryObject.has("location")) { | |||
| let locationsToPush = []; | |||
| queryObject.getAll("location").forEach((item) => { | |||
| locationsToPush.push(locations.find((p) => p.city === item)); | |||
| }); | |||
| setSelectedLocations([...locationsToPush]); | |||
| } else { | |||
| if (!myOffers) { | |||
| setSelectedLocations([]); | |||
| } | |||
| } | |||
| // dispatch(setIsAppliedStatus(true)); | |||
| }, [queryStringHook.queryString, categories, locations]); | |||
| // Apply everything | |||
| const applyFilters = () => { | |||
| makeQueryString(); | |||
| }; | |||
| // Clear function | |||
| const clearFilters = () => { | |||
| setSelectedLocations([]); | |||
| setSelectedSubcategory(); | |||
| setSelectedCategory(); | |||
| applyFilters(); | |||
| }; | |||
| // Helper function | |||
| const makeQueryString = () => { | |||
| let qsArray = []; | |||
| qsArray.push({ key: "category", value: selectedCategory?.name }); | |||
| qsArray.push({ key: "subcategory", value: selectedSubcategory?.name }); | |||
| selectedLocations?.forEach((location) => { | |||
| qsArray.push({ key: "location", value: location?.city }); | |||
| }); | |||
| qsArray.push({ key: "page", value: "1" }); | |||
| queryStringHook.appendMultipleToQueryString(qsArray); | |||
| }; | |||
| //Calculate chosen categories for number above filter icon on mobile responsive version | |||
| const calculateFiltersChosen = () => { | |||
| let sum = 0; | |||
| if (selectedCategory && selectedCategory?._id !== 0) { | |||
| sum++; | |||
| } | |||
| if (selectedSubcategory && selectedSubcategory?._id !== 0) { | |||
| sum++; | |||
| } | |||
| if (selectedLocations && selectedLocations?.length > 0) { | |||
| sum += selectedLocations.length; | |||
| } | |||
| return sum; | |||
| }; | |||
| // Setters | |||
| const setSelectedCategory = (payload) => { | |||
| if (isApplied !== false) { | |||
| // dispatch(setIsAppliedStatus(false)); | |||
| } | |||
| if (JSON.stringify(payload) !== JSON.stringify(selectedCategory)) { | |||
| dispatch(setFilteredCategory(payload)); | |||
| } | |||
| }; | |||
| const setSelectedSubcategory = (payload) => { | |||
| if (isApplied !== false) { | |||
| // dispatch(setIsAppliedStatus(false)); | |||
| } | |||
| if (JSON.stringify(payload) !== JSON.stringify(selectedSubcategory)) { | |||
| dispatch(setFilteredSubcategory(payload)); | |||
| } | |||
| }; | |||
| const clearSelectedSubcategory = () => { | |||
| setSelectedSubcategory(); | |||
| }; | |||
| const setSelectedLocations = (payload) => { | |||
| if (isApplied !== false) { | |||
| // dispatch(setIsAppliedStatus(false)); | |||
| } | |||
| if (JSON.stringify(payload) !== JSON.stringify(selectedLocations)) { | |||
| dispatch(setFilteredLocations(payload)); | |||
| } | |||
| }; | |||
| return { | |||
| selectedCategory, | |||
| setSelectedCategory, | |||
| selectedSubcategory, | |||
| setSelectedSubcategory, | |||
| clearSelectedSubcategory, | |||
| selectedLocations, | |||
| setSelectedLocations, | |||
| categories, | |||
| subcategories, | |||
| locations, | |||
| applyFilters, | |||
| clearFilters, | |||
| isApplied, | |||
| makeQueryString, | |||
| calculateFiltersChosen, | |||
| loadedFromQS, | |||
| setLoadedFromQS, | |||
| }; | |||
| }; | |||
| export default useFilters; | |||
| @@ -1,253 +0,0 @@ | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../constants/pages"; | |||
| import { KEY_PAGE, KEY_SIZE } from "../constants/queryStringConstants"; | |||
| import { sortEnum } from "../enums/sortEnum"; | |||
| import { fetchChats } from "../store/actions/chat/chatActions"; | |||
| import { | |||
| fetchMineOffers, | |||
| fetchOffers, | |||
| } from "../store/actions/offers/offersActions"; | |||
| import { | |||
| selectAppliedStatus, | |||
| selectSelectedCategory, | |||
| selectSelectedLocations, | |||
| selectSelectedSortOption, | |||
| selectSelectedSubcategory, | |||
| } from "../store/selectors/filtersSelectors"; | |||
| import { | |||
| selectMineOffers, | |||
| selectOffers, | |||
| selectPinnedOffers, | |||
| selectTotalOffers, | |||
| } from "../store/selectors/offersSelectors"; | |||
| import { useQueryString } from "./useQueryString"; | |||
| const useOffers = (myOffers) => { | |||
| const history = useHistory(); | |||
| const pinnedOffers = useSelector(selectPinnedOffers); | |||
| const offers = useSelector(selectOffers); | |||
| const mineOffers = useSelector(selectMineOffers); | |||
| const dispatch = useDispatch(); | |||
| const queryStringHook = useQueryString(); | |||
| const selectedCategory = useSelector(selectSelectedCategory); | |||
| const selectedSubcategory = useSelector(selectSelectedSubcategory); | |||
| const selectedLocations = useSelector(selectSelectedLocations); | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const isApplied = useSelector(selectAppliedStatus); | |||
| const total = useSelector(selectTotalOffers); | |||
| const [page, setPage] = useState(1); | |||
| const [searchQuery, setSearchQuery] = useState(""); | |||
| const [myOffersLength, setMyOffersLength] = useState(0); | |||
| //Fetching chats | |||
| useEffect(() => { | |||
| dispatch(fetchChats()); | |||
| }, []); | |||
| //Setting appropriate page based on query string | |||
| useEffect(() => { | |||
| let queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (queryObject.has(KEY_PAGE) && queryObject.get(KEY_PAGE) !== 1) { | |||
| setPage(parseInt(queryObject.get(KEY_PAGE))); | |||
| } | |||
| }, [history.location.search]); | |||
| // Checking if page is opened by clicking on logo on header, and fetching offers | |||
| // with empty query string if previous statement is true | |||
| useEffect(() => { | |||
| if (history?.location?.state?.logo || history?.location?.state?.refetch) { | |||
| dispatch(fetchOffers({ queryString: "" })); | |||
| queryStringHook.setQueryString(""); | |||
| setPage(1); | |||
| history.location.state = undefined; | |||
| } | |||
| }, [history.location.state]); | |||
| // Initialy loading offers with filters from query string | |||
| useEffect(() => { | |||
| if (queryStringHook.loadedFromURL) { | |||
| refetch(); | |||
| } else { | |||
| queryStringHook.appendMultipleToQueryString([ | |||
| { key: KEY_SIZE, value: "10" }, | |||
| { key: KEY_PAGE, value: "1" }, | |||
| ]); | |||
| } | |||
| }, [queryStringHook.loadedFromURL, queryStringHook.queryString]); | |||
| // Changing offers when page changes | |||
| useEffect(() => { | |||
| const queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (queryObject.has(KEY_PAGE)) { | |||
| if (queryObject.get(KEY_PAGE) !== page.toString()) { | |||
| queryStringHook.appendToQueryString(KEY_PAGE, page); | |||
| } else { | |||
| refetch(); | |||
| } | |||
| } else { | |||
| queryStringHook.appendToQueryString(KEY_PAGE, page); | |||
| } | |||
| }, [page]); | |||
| // All pinned to show when market place is opened | |||
| const pinnedOffersToShow = useMemo(() => { | |||
| if (myOffers) { | |||
| return mineOffers.filter((item) => item.pinned); | |||
| } | |||
| return pinnedOffers; | |||
| }, [pinnedOffers, mineOffers, page, myOffers]); | |||
| // Normal offers to show when market place is opened | |||
| const offersToShow = useMemo(() => { | |||
| if (myOffers) { | |||
| return mineOffers.filter((item) => item.pinned === false); | |||
| } | |||
| return offers; | |||
| }, [offers, mineOffers, page, myOffers]); | |||
| // Offers to show when market place is opened and when my offers are opened | |||
| const allOffersToShow = useMemo(() => { | |||
| let newOffers = [...pinnedOffersToShow, ...offersToShow]; | |||
| if (myOffers) { | |||
| // Filtering my offers based on category | |||
| if (selectedCategory && selectedCategory?._id !== 0) { | |||
| newOffers = newOffers.filter( | |||
| (item) => item.category.name === selectedCategory.name | |||
| ); | |||
| } | |||
| // Filtering my offers based on subcategory | |||
| if (selectedSubcategory && selectedSubcategory?._id !== 0) { | |||
| newOffers = newOffers.filter( | |||
| (item) => item.subcategory === selectedSubcategory.name | |||
| ); | |||
| } | |||
| // Filtering my offers based on locations | |||
| if (selectedLocations && selectedLocations?.length > 0) { | |||
| newOffers = newOffers.filter((item) => { | |||
| let isInOneOfLocations = false; | |||
| selectedLocations?.forEach((location) => { | |||
| if (item.location.city === location.city) { | |||
| isInOneOfLocations = true; | |||
| } | |||
| }); | |||
| return isInOneOfLocations; | |||
| }); | |||
| } | |||
| // Sorting my offers based on chosen sorting option | |||
| // Old offers are arrays used for sorting | |||
| let oldOffers = [...offersToShow]; | |||
| let oldPinnedOffers = [...pinnedOffersToShow]; | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.NEW.value | |||
| ) { | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemB._created) - new Date(itemA._created) | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemB._created) - new Date(itemA._created) | |||
| ), | |||
| ]; | |||
| } | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.OLD.value | |||
| ) { | |||
| newOffers = newOffers.sort( | |||
| (itemA, itemB) => new Date(itemA._created) - new Date(itemB._created) | |||
| ); | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemA._created) - new Date(itemB._created) | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemA._created) - new Date(itemB._created) | |||
| ), | |||
| ]; | |||
| } | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.POPULAR.value | |||
| ) { | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => itemB.views.count - itemA.views.count | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => itemB.views.count - itemA.views.count | |||
| ), | |||
| ]; | |||
| } | |||
| newOffers = newOffers.filter((item) => | |||
| item?.name?.toLowerCase().includes(searchQuery.toLowerCase(), 0) | |||
| ); | |||
| setMyOffersLength(newOffers?.length); | |||
| newOffers = newOffers.slice((page - 1) * 10, page * 10); | |||
| } | |||
| return newOffers; | |||
| }, [ | |||
| pinnedOffersToShow, | |||
| offersToShow, | |||
| myOffers, | |||
| page, | |||
| searchQuery, | |||
| isApplied, | |||
| ]); | |||
| // Total number of all offers that can be shown | |||
| const totalOffers = useMemo(() => { | |||
| if (myOffers) { | |||
| return myOffersLength; | |||
| } | |||
| return total; | |||
| }, [total, myOffersLength]); | |||
| const searchMyOffers = (searchValue) => { | |||
| setSearchQuery(searchValue); | |||
| }; | |||
| // Changing page | |||
| const handleDifferentPage = (pageNum) => { | |||
| setPage(pageNum); | |||
| }; | |||
| // Refetching offers based on query string | |||
| const refetch = () => { | |||
| if (!myOffers) { | |||
| dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString })); | |||
| history.replace({ | |||
| pathname: HOME_PAGE, | |||
| search: queryStringHook.getGlobalQueryString(), | |||
| }); | |||
| } else { | |||
| dispatch(fetchMineOffers()); | |||
| } | |||
| window.scrollTo({ | |||
| top: 0, | |||
| behavior: "smooth", | |||
| }); | |||
| const queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (queryObject.has(KEY_PAGE)) { | |||
| if (queryObject.get(KEY_PAGE) !== page.toString()) | |||
| setPage(parseInt(queryObject.get(KEY_PAGE))); | |||
| } else { | |||
| setPage(1); | |||
| } | |||
| }; | |||
| return { | |||
| handleDifferentPage, | |||
| totalOffers, | |||
| allOffersToShow, | |||
| page, | |||
| searchMyOffers, | |||
| }; | |||
| }; | |||
| export default useOffers; | |||
| @@ -0,0 +1,66 @@ | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { setFilteredCategory } from "../../store/actions/filters/filtersActions"; | |||
| import { selectCategories } from "../../store/selectors/categoriesSelectors"; | |||
| import { selectSelectedCategory } from "../../store/selectors/filtersSelectors"; | |||
| const useCategoryFilter = () => { | |||
| const selectedCategory = useSelector(selectSelectedCategory); | |||
| const allCategories = useSelector(selectCategories); | |||
| const dispatch = useDispatch(); | |||
| const [selectedCategoryLocally, setSelectedCategoryLocally] = useState({}); | |||
| const initialOption = useMemo(() => { | |||
| return { | |||
| _id: 0, | |||
| }; | |||
| }, []); | |||
| useEffect(() => { | |||
| setSelectedCategoryLocally(selectedCategory); | |||
| }, [selectedCategory]); | |||
| // Set selected category locally in state | |||
| // If second argument is true, then selected category is also updated in redux | |||
| const setSelectedCategory = (category, immediateApply = false) => { | |||
| setSelectedCategoryLocally(category); | |||
| if (immediateApply) { | |||
| dispatch(setFilteredCategory(category)); | |||
| } | |||
| }; | |||
| // Find category object by providing its name | |||
| const findCategory = (categoryName) => { | |||
| return allCategories.find((category) => category.name === categoryName); | |||
| }; | |||
| // Get all subcategories by providing its category name | |||
| const getSubcategories = (categoryName) => { | |||
| let category = findCategory(categoryName); | |||
| return category?.subcategories ? category.subcategories : []; | |||
| }; | |||
| // Update selected category in redux | |||
| const apply = () => { | |||
| dispatch(setFilteredCategory(selectedCategoryLocally)); | |||
| }; | |||
| // Clear category chosen | |||
| const clear = () => { | |||
| setSelectedCategoryLocally(initialOption); | |||
| dispatch(setFilteredCategory(initialOption)); | |||
| }; | |||
| return { | |||
| selectedCategory, | |||
| selectedCategoryLocally, | |||
| setSelectedCategory, | |||
| getSubcategories, | |||
| findCategory, | |||
| allCategories, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useCategoryFilter; | |||
| @@ -0,0 +1,50 @@ | |||
| import { useEffect, useMemo } from "react"; | |||
| import useCategoryFilter from "./useCategoryFilter"; | |||
| import useLocationsFilter from "./useLocationsFilter"; | |||
| import useSubcategoryFilter from "./useSubcategoryFilter"; | |||
| const useFilters = (clearAll = false) => { | |||
| const category = useCategoryFilter(); | |||
| const subcategory = useSubcategoryFilter(); | |||
| const locations = useLocationsFilter(); | |||
| useEffect(() => { | |||
| if (clearAll) { | |||
| clear(); | |||
| } | |||
| }, []); | |||
| const numOfFiltersChosen = useMemo(() => { | |||
| let sumOfFiltersChosen = 0; | |||
| if (category.selectedCategoryLocally?._id) sumOfFiltersChosen++; | |||
| if (subcategory.selectedSubcategoryLocally?._id) sumOfFiltersChosen++; | |||
| sumOfFiltersChosen += locations.selectedLocationsLocally.length; | |||
| return sumOfFiltersChosen; | |||
| }, [ | |||
| category.selectedCategoryLocally, | |||
| subcategory.selectedSubcategoryLocally, | |||
| locations.selectedLocationsLocally, | |||
| ]); | |||
| const apply = () => { | |||
| category.apply(); | |||
| subcategory.apply(); | |||
| locations.apply(); | |||
| }; | |||
| const clear = () => { | |||
| category.clear(); | |||
| subcategory.clear(); | |||
| locations.clear(); | |||
| }; | |||
| return { | |||
| category, | |||
| subcategory, | |||
| locations, | |||
| numOfFiltersChosen, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useFilters; | |||
| @@ -0,0 +1,54 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { setFilteredLocations } from "../../store/actions/filters/filtersActions"; | |||
| import { selectSelectedLocations } from "../../store/selectors/filtersSelectors"; | |||
| import { selectLocations } from "../../store/selectors/locationsSelectors"; | |||
| const useLocationsFilter = () => { | |||
| const selectedLocations = useSelector(selectSelectedLocations); | |||
| const dispatch = useDispatch(); | |||
| const allLocations = useSelector(selectLocations); | |||
| const [selectedLocationsLocally, setSelectedLocationsLocally] = useState([]); | |||
| useEffect(() => { | |||
| setSelectedLocationsLocally(selectedLocations); | |||
| }, [selectedLocations]); | |||
| // Set selected locations globally | |||
| const setSelectedLocations = (locations, immediateApply = false) => { | |||
| setSelectedLocationsLocally(locations); | |||
| if (immediateApply) { | |||
| dispatch(setFilteredLocations(locations)); | |||
| } | |||
| }; | |||
| // Find locations from array made from query string, and set locations globally | |||
| const setSelectedLocationsFromArray = (locations) => { | |||
| let locationsToPush = []; | |||
| locations.forEach((locationName) => { | |||
| locationsToPush.push(allLocations.find((p) => p.city === locationName)); | |||
| }); | |||
| setSelectedLocations([...locationsToPush]) | |||
| }; | |||
| const apply = () => { | |||
| dispatch(setFilteredLocations(selectedLocationsLocally)); | |||
| }; | |||
| const clear = () => { | |||
| setSelectedLocationsLocally([]); | |||
| dispatch(setFilteredLocations([])); | |||
| }; | |||
| return { | |||
| selectedLocations, | |||
| selectedLocationsLocally, | |||
| setSelectedLocations, | |||
| setSelectedLocationsFromArray, | |||
| allLocations, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useLocationsFilter; | |||
| @@ -0,0 +1,101 @@ | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { sortEnum } from "../../enums/sortEnum"; | |||
| import { fetchMineOffers } from "../../store/actions/offers/offersActions"; | |||
| import { selectMineOffers } from "../../store/selectors/offersSelectors"; | |||
| import useFilters from "./useFilters"; | |||
| import usePaging from "./usePaging"; | |||
| import useSearch from "./useSearch"; | |||
| import useSorting from "./useSorting"; | |||
| const useMyOffers = () => { | |||
| const filters = useFilters(true); | |||
| const sorting = useSorting(); | |||
| const mineOffers = useSelector(selectMineOffers); | |||
| const search = useSearch(); | |||
| const dispatch = useDispatch(); | |||
| const paging = usePaging(); | |||
| const [appliedFilters, setAppliedFilters] = useState(false); | |||
| const [totalOffers, setTotalOffers] = useState(0); | |||
| useEffect(() => { | |||
| dispatch(fetchMineOffers()); | |||
| }, []); | |||
| const apply = () => { | |||
| paging.changePage(1); | |||
| setAppliedFilters(false); | |||
| }; | |||
| // Filter, search and sort all mine offers | |||
| const allOffersToShow = useMemo(() => { | |||
| let mineOffersFiltered = [...mineOffers]; | |||
| // Filter mine offers by category | |||
| if (filters.category.selectedCategoryLocally?.name) | |||
| mineOffersFiltered = mineOffersFiltered.filter( | |||
| (offer) => | |||
| offer?.category?.name === | |||
| filters.category.selectedCategoryLocally.name | |||
| ); | |||
| // Filter mine offers by subcategory | |||
| if (filters.subcategory.selectedSubcategoryLocally?.name) { | |||
| mineOffersFiltered = mineOffersFiltered.filter( | |||
| (offer) => | |||
| offer?.subcategory === | |||
| filters.subcategory.selectedSubcategoryLocally?.name | |||
| ); | |||
| } | |||
| // Filter mine offers by locations | |||
| if (filters.locations.selectedLocationsLocally?.length > 0) { | |||
| mineOffersFiltered = mineOffersFiltered.filter((offer) => | |||
| filters.locations.selectedLocationsLocally.find( | |||
| (location) => location?.city === offer?.location?.city | |||
| ) | |||
| ); | |||
| } | |||
| // Sort mine offers | |||
| if (sorting.selectedSortOptionLocally.value !== sortEnum.INITIAL.value) { | |||
| if (sorting.selectedSortOptionLocally.value === sortEnum.OLD.value) { | |||
| mineOffersFiltered.sort( | |||
| (a, b) => new Date(a._created) - new Date(b._created) | |||
| ); | |||
| } | |||
| if (sorting.selectedSortOptionLocally.value === sortEnum.NEW.value) { | |||
| mineOffersFiltered.sort( | |||
| (a, b) => new Date(b._created) - new Date(a._created) | |||
| ); | |||
| } | |||
| if (sorting.selectedSortOptionLocally.value === sortEnum.POPULAR.value) { | |||
| mineOffersFiltered.sort((a, b) => b.views.count - a.views.count); | |||
| } | |||
| } | |||
| mineOffersFiltered = mineOffersFiltered.filter((offer) => | |||
| offer?.name?.toLowerCase()?.includes(search.searchStringLocally) | |||
| ); | |||
| setTotalOffers(mineOffersFiltered?.length); | |||
| mineOffersFiltered = mineOffersFiltered.slice( | |||
| (paging.currentPage - 1) * 10, | |||
| paging.currentPage * 10 | |||
| ); | |||
| if (!appliedFilters) { | |||
| setAppliedFilters(true); | |||
| } | |||
| return [...mineOffersFiltered]; | |||
| }, [ | |||
| appliedFilters, | |||
| sorting.selectedSortOptionLocally, | |||
| mineOffers, | |||
| paging.currentPage, | |||
| ]); | |||
| return { | |||
| filters, | |||
| paging, | |||
| sorting, | |||
| search, | |||
| allOffersToShow, | |||
| totalOffers, | |||
| apply, | |||
| }; | |||
| }; | |||
| export default useMyOffers; | |||
| @@ -0,0 +1,134 @@ | |||
| import { useEffect, useMemo } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { | |||
| KEY_CATEGORY, | |||
| KEY_LOCATION, | |||
| KEY_PAGE, | |||
| KEY_SEARCH, | |||
| KEY_SORTBY, | |||
| KEY_SUBCATEGORY, | |||
| } from "../../constants/queryStringConstants"; | |||
| import { fetchCategories } from "../../store/actions/categories/categoriesActions"; | |||
| import { fetchLocations } from "../../store/actions/locations/locationsActions"; | |||
| import { | |||
| selectOffers, | |||
| selectTotalOffers, | |||
| } from "../../store/selectors/offersSelectors"; | |||
| import useFilters from "./useFilters"; | |||
| import useQueryString from "./useQueryString"; | |||
| import { setQueryString } from "../../store/actions/queryString/queryStringActions"; | |||
| import { | |||
| convertQueryStringForBackend, | |||
| makeHeaderStringHelper, | |||
| makeQueryStringHelper, | |||
| } from "../../util/helpers/queryHelpers"; | |||
| import useSorting from "./useSorting"; | |||
| import useSearch from "./useSearch"; | |||
| import { | |||
| setHeaderString, | |||
| setSearchString, | |||
| } from "../../store/actions/filters/filtersActions"; | |||
| import usePaging from "./usePaging"; | |||
| const useOffers = () => { | |||
| const dispatch = useDispatch(); | |||
| const filters = useFilters(); | |||
| const queryStringHook = useQueryString(); | |||
| const offers = useSelector(selectOffers); | |||
| const totalOffers = useSelector(selectTotalOffers); | |||
| // Always fetch categories and locations, | |||
| // becouse count of total offers change over time | |||
| useEffect(() => { | |||
| dispatch(fetchCategories()); | |||
| dispatch(fetchLocations()); | |||
| return () => clear(); | |||
| }, []); | |||
| // On every change of query string, new header string should be created | |||
| // Header string is shown on Home page above offers | |||
| useEffect(() => { | |||
| const headerStringLocal = makeHeaderStringHelper(filters); | |||
| dispatch(setHeaderString(headerStringLocal)); | |||
| }, [queryStringHook.queryString]); | |||
| // Initially set category, location and subcategory based on query string | |||
| useEffect(() => { | |||
| if (queryStringHook.isInitiallyLoaded) { | |||
| const queryObject = queryStringHook.queryObject; | |||
| if (KEY_CATEGORY in queryObject) { | |||
| const category = filters.category.findCategory( | |||
| queryObject[KEY_CATEGORY] | |||
| ); | |||
| filters.category.setSelectedCategory(category); | |||
| if (KEY_SUBCATEGORY in queryObject) { | |||
| const subcategory = filters.category | |||
| .getSubcategories(category?.name) | |||
| .find( | |||
| (subcategory) => subcategory.name === queryObject[KEY_SUBCATEGORY] | |||
| ); | |||
| filters.subcategory.setSelectedSubcategory(subcategory); | |||
| } | |||
| } | |||
| if (KEY_LOCATION in queryObject) { | |||
| filters.locations.setSelectedLocationsFromArray( | |||
| queryObject[KEY_LOCATION] | |||
| ); | |||
| } | |||
| if (KEY_SORTBY in queryObject) { | |||
| sorting.changeSortingFromName(queryObject[KEY_SORTBY]); | |||
| } | |||
| if (KEY_PAGE in queryObject) { | |||
| if (queryObject[KEY_PAGE] !== 1) | |||
| paging.changePage(queryObject[KEY_PAGE]); | |||
| } | |||
| dispatch(setSearchString(queryObject[KEY_SEARCH])); | |||
| } | |||
| }, [queryStringHook.isInitiallyLoaded]); | |||
| const allOffersToShow = useMemo(() => { | |||
| return offers; | |||
| }, [offers]); | |||
| const apply = () => { | |||
| filters.apply(); | |||
| const newQueryString = makeQueryStringHelper( | |||
| filters, | |||
| paging, | |||
| search, | |||
| sorting | |||
| ); | |||
| dispatch(setQueryString(convertQueryStringForBackend(newQueryString))); | |||
| }; | |||
| // Those hooks are below becouse function apply cannot be put on props before initialization | |||
| const sorting = useSorting(apply); | |||
| const paging = usePaging(apply); | |||
| const search = useSearch(apply); | |||
| // On every change of search string, offers should be immediately searched | |||
| useEffect(() => { | |||
| if (queryStringHook.isInitiallyLoaded) { | |||
| search.searchOffers(search.searchString); | |||
| } | |||
| }, [search.searchString]); | |||
| const clear = () => { | |||
| filters.clear(); | |||
| sorting.clear(); | |||
| paging.changePage(1); | |||
| }; | |||
| return { | |||
| filters, | |||
| sorting, | |||
| paging, | |||
| queryStringHook, | |||
| allOffersToShow, | |||
| totalOffers, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useOffers; | |||
| @@ -0,0 +1,38 @@ | |||
| import { useEffect, useState } from "react"; | |||
| const usePaging = (applyAllFilters) => { | |||
| const [currentPage, setCurrentPage] = useState(1); | |||
| const [isInitallyLoaded, setIsInitiallyLoaded] = useState(false); | |||
| // If state currentPage is changed, new request to backend should be sent, | |||
| // except on initial load | |||
| useEffect(() => { | |||
| if (isInitallyLoaded && applyAllFilters) { | |||
| applyAllFilters(); | |||
| } | |||
| window.scrollTo({ | |||
| top: 0, | |||
| behavior: "smooth", | |||
| }); | |||
| }, [currentPage]); | |||
| const changePage = (pageNumber) => { | |||
| setCurrentPage(pageNumber); | |||
| setIsInitiallyLoaded(true); | |||
| }; | |||
| const goToNextPage = () => { | |||
| setCurrentPage((prevPage) => prevPage + 1); | |||
| }; | |||
| const goToPrevPage = () => { | |||
| setCurrentPage((prevPage) => prevPage - 1); | |||
| }; | |||
| return { | |||
| currentPage, | |||
| changePage, | |||
| goToNextPage, | |||
| goToPrevPage, | |||
| }; | |||
| }; | |||
| export default usePaging; | |||
| @@ -0,0 +1,60 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { fetchOffers } from "../../store/actions/offers/offersActions"; | |||
| import { setQueryString } from "../../store/actions/queryString/queryStringActions"; | |||
| import { selectQueryString } from "../../store/selectors/queryStringSelectors"; | |||
| import { | |||
| convertQueryStringForBackend, | |||
| convertQueryStringForFrontend, | |||
| getQueryObjectHelper, | |||
| } from "../../util/helpers/queryHelpers"; | |||
| const useQueryString = () => { | |||
| const queryString = useSelector(selectQueryString); | |||
| const history = useHistory(); | |||
| const dispatch = useDispatch(); | |||
| const [isInitiallyLoaded, setIsInitallyLoaded] = useState(false); | |||
| const [queryObject, setQueryObject] = useState({}); | |||
| // Initially read filters, sorting and paging from querystring | |||
| useEffect(() => { | |||
| if ((!isInitiallyLoaded || history.location?.state?.logo) && !history.location?.state?.from) { | |||
| const queryStringFromUrl = history.location?.search; | |||
| setQueryObject(getQueryObjectHelper(queryStringFromUrl)); | |||
| dispatch(setQueryString(queryStringFromUrl)); | |||
| } | |||
| history.location.state = {} | |||
| }, [history.location]); | |||
| // Set initially loaded to true on initial load | |||
| useEffect(() => { | |||
| if ( | |||
| convertQueryStringForFrontend(queryString) === | |||
| convertQueryStringForFrontend(history.location.search) && | |||
| !isInitiallyLoaded | |||
| ) { | |||
| setIsInitallyLoaded(true); | |||
| } | |||
| }, [queryString]); | |||
| // Updating offers on query string change | |||
| useEffect(() => { | |||
| if (isInitiallyLoaded) { | |||
| dispatch( | |||
| fetchOffers({ queryString: convertQueryStringForBackend(queryString) }) | |||
| ); | |||
| setQueryObject(getQueryObjectHelper(queryString)); | |||
| history.replace({ | |||
| search: convertQueryStringForFrontend(queryString), | |||
| }); | |||
| } | |||
| }, [queryString, isInitiallyLoaded]); | |||
| return { | |||
| queryString, | |||
| queryObject, | |||
| isInitiallyLoaded, | |||
| }; | |||
| }; | |||
| export default useQueryString; | |||
| @@ -0,0 +1,46 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { setSearchString } from "../../store/actions/filters/filtersActions"; | |||
| import { selectSearchString } from "../../store/selectors/filtersSelectors"; | |||
| const useSearch = (applyAllFilters) => { | |||
| const [searchStringLocally, setSearchStringLocally] = useState(""); | |||
| const [isInitallyLoaded, setIsInitiallyLoaded] = useState(false); | |||
| const dispatch = useDispatch(); | |||
| const searchString = useSelector(selectSearchString); | |||
| // On every global change of search string, new request to backend should be sent | |||
| useEffect(() => { | |||
| if (searchStringLocally !== searchString && applyAllFilters) { | |||
| setSearchStringLocally(searchString); | |||
| } | |||
| if (isInitallyLoaded) { | |||
| if (applyAllFilters) applyAllFilters(); | |||
| } | |||
| }, [searchString]); | |||
| // On every local change of search string, global state of search string should be also updated | |||
| useEffect(() => { | |||
| if (isInitallyLoaded && applyAllFilters) { | |||
| dispatch(setSearchString(searchStringLocally)); | |||
| } | |||
| }, [searchStringLocally]); | |||
| const searchOffers = (searchValue) => { | |||
| setIsInitiallyLoaded(true); | |||
| setSearchStringLocally(searchValue); | |||
| }; | |||
| const clear = () => { | |||
| setSearchStringLocally(""); | |||
| }; | |||
| return { | |||
| searchOffers, | |||
| setSearchStringLocally, | |||
| searchStringLocally, | |||
| searchString, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useSearch; | |||
| @@ -0,0 +1,65 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { | |||
| VALUE_SORTBY_NEW, | |||
| VALUE_SORTBY_OLD, | |||
| VALUE_SORTBY_POPULAR, | |||
| } from "../../constants/queryStringConstants"; | |||
| import { sortEnum } from "../../enums/sortEnum"; | |||
| import { setFilteredSortOption } from "../../store/actions/filters/filtersActions"; | |||
| import { selectSelectedSortOption } from "../../store/selectors/filtersSelectors"; | |||
| const useSorting = (applyAllFilters) => { | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const [selectedSortOptionLocally, setSelectedSortOptionLocally] = useState( | |||
| sortEnum.INITIAL | |||
| ); | |||
| const [isInitiallyLoaded, setIsInitallyLoaded] = useState(false); | |||
| const dispatch = useDispatch(); | |||
| // On every change of sorting option, new request to backend should be sent | |||
| useEffect(() => { | |||
| if (isInitiallyLoaded) { | |||
| if (applyAllFilters) applyAllFilters(); | |||
| } | |||
| }, [isInitiallyLoaded, selectedSortOption]); | |||
| const changeSorting = (newSortOption) => { | |||
| dispatch(setFilteredSortOption(newSortOption)); | |||
| setSelectedSortOptionLocally(newSortOption); | |||
| if (!isInitiallyLoaded) { | |||
| setIsInitallyLoaded(true); | |||
| } | |||
| }; | |||
| // Change sorting by name of sorting option that is shown on frontned | |||
| const changeSortingFromName = (sortingName) => { | |||
| if (sortingName === VALUE_SORTBY_NEW) { | |||
| changeSorting(sortEnum.NEW, true); | |||
| } | |||
| if (sortingName === VALUE_SORTBY_OLD) { | |||
| changeSorting(sortEnum.OLD, true); | |||
| } | |||
| if (sortingName === VALUE_SORTBY_POPULAR) { | |||
| changeSorting(sortEnum.POPULAR, true); | |||
| } | |||
| }; | |||
| const apply = () => { | |||
| // For future changes | |||
| }; | |||
| const clear = () => { | |||
| dispatch(setFilteredSortOption(sortEnum.INITIAL)); | |||
| }; | |||
| return { | |||
| selectedSortOption, | |||
| selectedSortOptionLocally, | |||
| changeSorting, | |||
| changeSortingFromName, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useSorting; | |||
| @@ -0,0 +1,55 @@ | |||
| import { useEffect, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { setFilteredSubcategory } from "../../store/actions/filters/filtersActions"; | |||
| import { selectSelectedSubcategory } from "../../store/selectors/filtersSelectors"; | |||
| const useSubcategoryFilter = () => { | |||
| const selectedSubcategory = useSelector(selectSelectedSubcategory); | |||
| const dispatch = useDispatch(); | |||
| const initialOption = { | |||
| label: "SVE PODKATEGORIJE", | |||
| _id: 0, | |||
| }; | |||
| const [selectedSubcategoryLocally, setSelectedSubcategoryLocally] = | |||
| useState(initialOption); | |||
| useEffect(() => { | |||
| if (selectedSubcategory) | |||
| if ("_id" in selectedSubcategory) { | |||
| setSelectedSubcategoryLocally(selectedSubcategory); | |||
| } | |||
| }, [selectedSubcategory]); | |||
| useEffect(() => { | |||
| if (Object.keys(selectedSubcategoryLocally)?.length === 0) { | |||
| setSelectedSubcategoryLocally(initialOption); | |||
| } | |||
| }, [initialOption]); | |||
| const setSelectedSubcategory = (subcategory, immediateApply = false) => { | |||
| setSelectedSubcategoryLocally(subcategory); | |||
| if (immediateApply) { | |||
| dispatch(setFilteredSubcategory(subcategory)); | |||
| } | |||
| }; | |||
| const apply = () => { | |||
| dispatch(setFilteredSubcategory(selectedSubcategoryLocally)); | |||
| }; | |||
| const clear = () => { | |||
| setSelectedSubcategoryLocally(initialOption); | |||
| dispatch(setFilteredSubcategory(initialOption)); | |||
| }; | |||
| return { | |||
| selectedSubcategory, | |||
| selectedSubcategoryLocally, | |||
| setSelectedSubcategory, | |||
| initialOption, | |||
| apply, | |||
| clear, | |||
| }; | |||
| }; | |||
| export default useSubcategoryFilter; | |||
| @@ -1,174 +0,0 @@ | |||
| /* eslint-disable */ | |||
| import _ from "lodash"; | |||
| import { useEffect, useMemo, useState } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import PropTypes from "prop-types"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { HOME_PAGE } from "../constants/pages"; | |||
| import { setQueryString as setQueryStringSaga } from "../store/actions/queryString/queryStringActions"; | |||
| import { selectQueryString } from "../store/selectors/queryStringSelectors"; | |||
| import { | |||
| convertQueryStringBackend, | |||
| convertQueryStringFrontend, | |||
| } from "../util/helpers/queryHelpers"; | |||
| import { KEY_CATEGORY, KEY_LOCATION, KEY_PAGE, KEY_SIZE, KEY_SORTBY, KEY_SORT_DATE, KEY_SORT_POPULAR, KEY_SUBCATEGORY } from "../constants/queryStringConstants"; | |||
| export const useQueryString = () => { | |||
| const queryString = useSelector(selectQueryString); | |||
| const history = useHistory(); | |||
| const [globalQueryString, setGlobalQueryString] = useState(""); | |||
| const [initial, setInitial] = useState(true); | |||
| const [loadedFromURL, setLoadedFromURL] = useState(false); | |||
| const dispatch = useDispatch(); | |||
| // Initially loading query string and putting it into redux | |||
| useEffect(() => { | |||
| // Substring(1) used becouse query string in initial state is ?key=value | |||
| // and in redux query string is stored as only key=value | |||
| const queryStringLocal = history.location.search.substring(1); | |||
| setQueryString(convertQueryStringBackend(queryStringLocal)); | |||
| setGlobalQueryString(queryStringLocal); | |||
| }, []); | |||
| // Setting loadedFromUrl to true when query string is loaded and filters/sorting/search | |||
| // has been changed so global query string matches current query string | |||
| useEffect(() => { | |||
| if (globalQueryString === history.location.search.substring(1)) | |||
| setLoadedFromURL(true); | |||
| }, [globalQueryString]); | |||
| // Making function to initially set global query string when all filters and sorting | |||
| // has been set | |||
| const fun = useMemo(() => { | |||
| return _.once(() => { | |||
| setGlobalQueryString(convertQueryStringFrontend(queryString)); | |||
| setInitial(false); | |||
| }); | |||
| }, [queryString]); | |||
| // Setting global query string when all filters and sorting has been set | |||
| useEffect(() => { | |||
| if (initial && loadedFromURL) { | |||
| if (queryString?.length > 0) { | |||
| fun(); | |||
| } | |||
| } else { | |||
| setGlobalQueryString(convertQueryStringFrontend(queryString)); | |||
| } | |||
| }, [queryString, loadedFromURL]); | |||
| // When global query string is changed, updating query string that user sees | |||
| useEffect(() => { | |||
| if (!initial && history.location.pathname === HOME_PAGE) { | |||
| history.replace({ | |||
| pathname: HOME_PAGE, | |||
| search: "?" + globalQueryString, | |||
| }); | |||
| } | |||
| }, [globalQueryString, initial]); | |||
| const getQueryString = () => { | |||
| return queryString; | |||
| }; | |||
| const setQueryString = (newQueryString) => { | |||
| dispatch(setQueryStringSaga(newQueryString)); | |||
| }; | |||
| const getQueryObject = () => { | |||
| const urlParams = new URLSearchParams(queryString); | |||
| return Object.fromEntries(urlParams); | |||
| }; | |||
| // Adding key-value pairs to query string, deletes its previous duplicates, except | |||
| // when working with locations, then just appends it, or doesnt append if query string | |||
| // already contains provided location | |||
| const appendToQueryString = (key, value) => { | |||
| if (loadedFromURL) { | |||
| let urlParams = new URLSearchParams(queryString); | |||
| if (key === KEY_LOCATION) { | |||
| if (urlParams.has(key)) { | |||
| let arrayOfLocations = urlParams.getAll(key); | |||
| if (arrayOfLocations.includes(value)) { | |||
| arrayOfLocations = arrayOfLocations.filter( | |||
| (item) => item?.toString() !== value?.toString() | |||
| ); | |||
| urlParams.delete(key); | |||
| arrayOfLocations.forEach((item) => { | |||
| urlParams.append(key, item); | |||
| }); | |||
| } | |||
| } | |||
| } else { | |||
| if (urlParams.has(key)) { | |||
| urlParams.delete(key); | |||
| } | |||
| } | |||
| if (!value) setQueryString(urlParams.toString()); | |||
| urlParams.append(key, value); | |||
| setQueryString(urlParams.toString()); | |||
| return urlParams.toString(); | |||
| } | |||
| }; | |||
| // Same as appendToQueryString, just adds multiple key-value pairs at once | |||
| const appendMultipleToQueryString = (array = []) => { | |||
| if (loadedFromURL) { | |||
| let urlParams = new URLSearchParams(queryString); | |||
| if ( | |||
| array.find((item) => item.key === KEY_CATEGORY) || | |||
| array.find((item) => item.key === KEY_SUBCATEGORY) | |||
| ) { | |||
| urlParams.delete(KEY_LOCATION); | |||
| } | |||
| array.forEach((item) => { | |||
| if (urlParams.has(item.key) && item.key !== KEY_LOCATION) { | |||
| urlParams.delete(item.key); | |||
| } | |||
| if (!item.value) return; | |||
| urlParams.append(item.key, item.value); | |||
| }); | |||
| setQueryString(urlParams.toString()); | |||
| return urlParams.toString(); | |||
| } | |||
| }; | |||
| const deleteFromQueryString = (key, value = null) => { | |||
| let urlParams = new URLSearchParams(queryString); | |||
| if (key === KEY_LOCATION) { | |||
| let arrayOfLocations = urlParams.getAll(key); | |||
| arrayOfLocations = arrayOfLocations.filter((item) => item !== value); | |||
| urlParams.delete(key); | |||
| arrayOfLocations.forEach((item) => { | |||
| urlParams.append(key, item); | |||
| }); | |||
| } else if (key === KEY_SORTBY) { | |||
| urlParams.delete(KEY_SORT_DATE); | |||
| urlParams.delete(KEY_SORT_POPULAR); | |||
| } else { | |||
| urlParams.delete(key); | |||
| } | |||
| setQueryString(urlParams.toString()); | |||
| return urlParams.toString(); | |||
| }; | |||
| const getInitialQueryString = () => { | |||
| let urlParams = new URLSearchParams(queryString); | |||
| urlParams = new URLSearchParams(appendToQueryString(KEY_SIZE, 10)); | |||
| urlParams = new URLSearchParams(appendToQueryString(KEY_PAGE, 1)); | |||
| return urlParams; | |||
| }; | |||
| const getGlobalQueryString = () => { | |||
| return globalQueryString; | |||
| }; | |||
| return { | |||
| queryString, | |||
| globalQueryString, | |||
| getQueryString, | |||
| setQueryString, | |||
| getQueryObject, | |||
| initial, | |||
| loadedFromURL, | |||
| appendMultipleToQueryString, | |||
| getGlobalQueryString, | |||
| appendToQueryString, | |||
| getInitialQueryString, | |||
| deleteFromQueryString, | |||
| }; | |||
| }; | |||
| @@ -1,16 +1,16 @@ | |||
| import { useQueryString } from "./useQueryString"; | |||
| // import useQueryString from "./useOffers/useQueryString"; | |||
| export const useSearch = () => { | |||
| const queryStringHook = useQueryString(); | |||
| const searchOffers = (searchString) => { | |||
| if (searchString?.length !== 0) { | |||
| queryStringHook.appendToQueryString("oname", searchString); | |||
| } else { | |||
| const newQueryString = new URLSearchParams(queryStringHook.queryString); | |||
| if (newQueryString.has("oname")) { | |||
| queryStringHook.deleteFromQueryString("oname"); | |||
| } | |||
| } | |||
| // const queryStringHook = useQueryString(); | |||
| const searchOffers = () => { | |||
| // if (searchString?.length !== 0) { | |||
| // queryStringHook.appendToQueryString("oname", searchString); | |||
| // } else { | |||
| // const newQueryString = new URLSearchParams(queryStringHook.queryString); | |||
| // if (newQueryString.has("oname")) { | |||
| // queryStringHook.deleteFromQueryString("oname"); | |||
| // } | |||
| // } | |||
| }; | |||
| return { | |||
| @@ -0,0 +1,36 @@ | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| const useSkeleton = (skeletonOptions) => { | |||
| const [transitionStage, setTransitionStage] = useState(1); | |||
| // After how long skeleton changes screen | |||
| const timeoutInterval = useMemo(() => { | |||
| return skeletonOptions?.timeoutInterval || 900; | |||
| }); | |||
| // isLoadingIndicator is boolean that indicates should skeleton screen be shown | |||
| const isLoadingIndicator = useMemo(() => { | |||
| return skeletonOptions?.isLoadingIndicator; | |||
| }, [skeletonOptions]); | |||
| // Timeout function to change transition stage | |||
| const timeout = useCallback(() => { | |||
| setTransitionStage((prevTransitionStage) => { | |||
| if (prevTransitionStage === 2) return 1; | |||
| return prevTransitionStage + 1; | |||
| }); | |||
| }, [transitionStage]); | |||
| useEffect(() => { | |||
| let newTimeout; | |||
| if (isLoadingIndicator) { | |||
| newTimeout = setTimeout(timeout, timeoutInterval); | |||
| } | |||
| return () => clearTimeout(newTimeout); | |||
| }, [timeout, isLoadingIndicator]); | |||
| return { | |||
| transitionStage, | |||
| }; | |||
| }; | |||
| export default useSkeleton; | |||
| @@ -1,86 +0,0 @@ | |||
| import { useEffect } from "react"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { sortEnum } from "../enums/sortEnum"; | |||
| import { setFilteredSortOption } from "../store/actions/filters/filtersActions"; | |||
| import { selectSelectedSortOption } from "../store/selectors/filtersSelectors"; | |||
| import { useQueryString } from "./useQueryString"; | |||
| import { convertQueryStringFrontend } from "../util/helpers/queryHelpers"; | |||
| import { | |||
| KEY_PAGE, | |||
| KEY_SORTBY, | |||
| KEY_SORT_DATE, | |||
| KEY_SORT_POPULAR, | |||
| VALUE_SORTBY_NEW, | |||
| VALUE_SORTBY_OLD, | |||
| VALUE_SORTBY_POPULAR, | |||
| } from "../constants/queryStringConstants"; | |||
| const useSorting = () => { | |||
| const dispatch = useDispatch(); | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const sortOptions = sortEnum; | |||
| const queryStringHook = useQueryString(); | |||
| // Setting sort option on initially load or refresh page | |||
| useEffect(() => { | |||
| if (queryStringHook.loadedFromURL) { | |||
| const queryString = queryStringHook.queryString; | |||
| let queryObject = new URLSearchParams( | |||
| convertQueryStringFrontend(queryString) | |||
| ); | |||
| if (queryObject.has(KEY_SORTBY)) { | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_NEW) { | |||
| setSelectedSortOption(sortEnum.NEW); | |||
| } | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_OLD) { | |||
| setSelectedSortOption(sortEnum.OLD); | |||
| } | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_POPULAR) { | |||
| setSelectedSortOption(sortEnum.POPULAR); | |||
| } | |||
| } else { | |||
| setSelectedSortOption(sortOptions.INITIAL); | |||
| } | |||
| } | |||
| }, [queryStringHook.queryString, queryStringHook.loadedFromURL]); | |||
| const setSelectedSortOption = (payload, shouldGoFirstPage = false) => { | |||
| dispatch(setFilteredSortOption(payload)); | |||
| let _des_date = null; | |||
| let _des_popular = null; | |||
| if (payload.value === sortOptions.NEW.value) { | |||
| _des_date = true; | |||
| } | |||
| if (payload.value === sortOptions.OLD.value) { | |||
| _des_date = false; | |||
| } | |||
| if (payload.value === sortOptions.POPULAR.value) { | |||
| _des_popular = true; | |||
| } | |||
| let queryArray = []; | |||
| if (_des_date !== null) { | |||
| queryArray.push({ key: KEY_SORT_DATE, value: `${_des_date}` }); | |||
| queryArray.push({ key: KEY_SORT_POPULAR }); | |||
| } | |||
| if (_des_popular !== null) { | |||
| queryArray.push({ key: KEY_SORT_POPULAR, value: `${_des_popular}` }); | |||
| queryArray.push({ key: KEY_SORT_DATE }); | |||
| } | |||
| if (shouldGoFirstPage) { | |||
| queryArray.push({ key: KEY_PAGE, value: "1" }); | |||
| } | |||
| queryStringHook.appendMultipleToQueryString(queryArray); | |||
| }; | |||
| const changeSorting = (payload) => { | |||
| setSelectedSortOption(payload, true); | |||
| }; | |||
| return { | |||
| selectedSortOption, | |||
| setSelectedSortOption, | |||
| sortOptions, | |||
| changeSorting, | |||
| }; | |||
| }; | |||
| export default useSorting; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useCallback, useEffect, useState } 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"; | |||
| @@ -6,40 +6,41 @@ 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"; | |||
| import useSkeleton from "../../hooks/useSkeleton"; | |||
| const HomePage = () => { | |||
| const [animationStage, setAnimationStage] = useState(1); | |||
| const isLoadingOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_SCOPE) | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const offers = useOffers(); | |||
| const { transitionStage } = useSkeleton({ | |||
| timeoutInterval: 900, | |||
| isLoadingIndicator: isLoadingOffers, | |||
| }); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| const timeout = useCallback(() => { | |||
| setAnimationStage((prevAnimationStage) => { | |||
| if (prevAnimationStage === 2) return 1; | |||
| return prevAnimationStage + 1; | |||
| }); | |||
| }, [animationStage]); | |||
| useEffect(() => { | |||
| let newTimeout; | |||
| if (isLoadingOffers) { | |||
| newTimeout = setTimeout(timeout, 900); | |||
| } | |||
| return () => clearTimeout(newTimeout); | |||
| }, [timeout, isLoadingOffers]); | |||
| return ( | |||
| <HomePageContainer> | |||
| <MainLayout | |||
| leftCard={ | |||
| <FilterCard | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| animationStage={animationStage} | |||
| animationStage={transitionStage} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| animationStage={animationStage} | |||
| animationStage={transitionStage} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| @@ -1,16 +1,51 @@ | |||
| import React from "react"; | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { MyOffersContainer } from "./MyOffers.styled"; | |||
| 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"; | |||
| const MyOffers = () => { | |||
| const offers = useMyOffers(); | |||
| const isLoadingMineOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_MINE_SCOPE) | |||
| ); | |||
| const [filtersOpened, setFiltersOpened] = useState(false); | |||
| const { transitionStage } = useSkeleton({ | |||
| timeoutInterval: 900, | |||
| isLoadingIndicator: isLoadingMineOffers, | |||
| }); | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| return ( | |||
| <MyOffersContainer> | |||
| <MainLayout | |||
| leftCard={<FilterCard myOffers />} | |||
| content={<MarketPlace myOffers={true} />} | |||
| leftCard={ | |||
| <FilterCard | |||
| myOffers | |||
| offers={offers} | |||
| animationStage={transitionStage} | |||
| filtersOpened={filtersOpened} | |||
| toggleFilters={toggleFilters} | |||
| skeleton={isLoadingMineOffers} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| myOffers={true} | |||
| offers={offers} | |||
| animationStage={transitionStage} | |||
| skeleton={isLoadingMineOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| </MyOffersContainer> | |||
| ); | |||
| @@ -4,7 +4,7 @@ import queryString from "qs"; | |||
| const request = axios.create({ | |||
| // baseURL: "http://192.168.88.150:3001/", | |||
| // baseURL: "http://192.168.88.175:3005/", | |||
| baseURL: "https://trampa-api.dilig.net/", | |||
| baseURL: "https://trampa-api-test.dilig.net/", | |||
| headers: { | |||
| "Content-Type": "application/json", | |||
| @@ -9,3 +9,5 @@ export const SET_LOCATIONS = createSetType("FILTERS_SET_LOCATIONS"); | |||
| export const SET_SORT_OPTION = createSetType("FILTERS_SET_SORT_OPTION"); | |||
| export const SET_IS_APPLIED = createSetType("FILTERS_SET_IS_APPLIED"); | |||
| export const SET_QUERY_STRING = createSetType("FILTERS_SET_QUERY_STRING"); | |||
| export const SET_HEADER_STRING = createSetType("FILTERS_SET_HEADER_STRING"); | |||
| export const SET_SEARCH_STRING = createSetType("FILTERS_SET_SEARCH"); | |||
| @@ -1,4 +1,4 @@ | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_IS_APPLIED, SET_LOCATIONS, SET_QUERY_STRING, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_HEADER_STRING, SET_IS_APPLIED, SET_LOCATIONS, SET_QUERY_STRING, SET_SEARCH_STRING, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants"; | |||
| export const setFilters = (payload) => ({ | |||
| type: SET_FILTERS, | |||
| @@ -30,4 +30,12 @@ export const setIsAppliedStatus = (payload) => ({ | |||
| export const setQueryString = (payload) => ({ | |||
| type: SET_QUERY_STRING, | |||
| payload, | |||
| }) | |||
| export const setHeaderString = (payload) => ({ | |||
| type: SET_HEADER_STRING, | |||
| payload | |||
| }) | |||
| export const setSearchString = (payload) => ({ | |||
| type: SET_SEARCH_STRING, | |||
| payload | |||
| }) | |||
| @@ -1,15 +1,20 @@ | |||
| import { applyMiddleware, compose, createStore } from 'redux'; | |||
| import createSagaMiddleware from 'redux-saga'; | |||
| import rootReducer from './reducers'; | |||
| import rootSaga from './saga'; | |||
| import loadingMiddleware from './middleware/loadingMiddleware'; | |||
| import requestStatusMiddleware from './middleware/requestStatusMiddleware'; | |||
| import internalServerErrorMiddleware from './middleware/internalServerErrorMiddleware'; | |||
| import persistStore from 'redux-persist/es/persistStore'; | |||
| import accessTokensMiddleware from './middleware/accessTokensMiddleware'; | |||
| import { applyMiddleware, compose, createStore } from "redux"; | |||
| import createSagaMiddleware from "redux-saga"; | |||
| import rootReducer from "./reducers"; | |||
| import rootSaga from "./saga"; | |||
| import loadingMiddleware from "./middleware/loadingMiddleware"; | |||
| import requestStatusMiddleware from "./middleware/requestStatusMiddleware"; | |||
| import internalServerErrorMiddleware from "./middleware/internalServerErrorMiddleware"; | |||
| import persistStore from "redux-persist/es/persistStore"; | |||
| import accessTokensMiddleware from "./middleware/accessTokensMiddleware"; | |||
| const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; | |||
| const composeEnhancers = | |||
| (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ && | |||
| window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ | |||
| trace: true, | |||
| traceLimit: 25, | |||
| })) || | |||
| compose; | |||
| const sagaMiddleware = createSagaMiddleware(); | |||
| export const store = createStore( | |||
| rootReducer, | |||
| @@ -20,8 +25,8 @@ export const store = createStore( | |||
| requestStatusMiddleware, | |||
| internalServerErrorMiddleware, | |||
| accessTokensMiddleware | |||
| ), | |||
| ), | |||
| ) | |||
| ) | |||
| ); | |||
| export const persistor = persistStore(store); | |||
| @@ -2,8 +2,10 @@ import { | |||
| CLEAR_FILTERS, | |||
| SET_CATEGORY, | |||
| SET_FILTERS, | |||
| SET_HEADER_STRING, | |||
| SET_IS_APPLIED, | |||
| SET_LOCATIONS, | |||
| SET_SEARCH_STRING, | |||
| SET_SORT_OPTION, | |||
| SET_SUBCATEGORY, | |||
| } from "../../actions/filters/filtersActionConstants"; | |||
| @@ -17,6 +19,8 @@ const initialState = { | |||
| sortOption: null, | |||
| isApplied: false, | |||
| queryString: "", | |||
| headerString: "", | |||
| searchString: "" | |||
| }, | |||
| }; | |||
| @@ -29,6 +33,8 @@ export default createReducer( | |||
| [SET_LOCATIONS]: setFilteredLocations, | |||
| [SET_SORT_OPTION]: setFilteredSortOption, | |||
| [SET_IS_APPLIED]: setIsAppliedStatus, | |||
| [SET_HEADER_STRING]: setHeaderString, | |||
| [SET_SEARCH_STRING]: setSearchString, | |||
| }, | |||
| initialState | |||
| ); | |||
| @@ -91,3 +97,21 @@ function setIsAppliedStatus(state, {payload}) { | |||
| } | |||
| } | |||
| } | |||
| function setHeaderString(state, {payload}) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| headerString: payload | |||
| } | |||
| } | |||
| } | |||
| function setSearchString(state, {payload}) { | |||
| return { | |||
| ...state, | |||
| filters: { | |||
| ...state.filters, | |||
| searchString: payload | |||
| } | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ import { QUERY_STRING_SET_REDUX } from "../../actions/queryString/queryStringAct | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| queryString: "", | |||
| queryString: "" | |||
| }; | |||
| export default createReducer( | |||
| @@ -38,7 +38,7 @@ import { | |||
| attemptFetchProfileOffers, | |||
| attemptFetchMoreOffers, | |||
| } from "../../request/offersRequest"; | |||
| import { convertQueryStringBackend } from "../../util/helpers/queryHelpers"; | |||
| import { convertQueryStringForBackend } from "../../util/helpers/queryHelpers"; | |||
| // import { setQueryString } from "../actions/filters/filtersActions"; | |||
| import { | |||
| OFFERS_FETCH_MORE, | |||
| @@ -66,7 +66,7 @@ function* fetchOffers(payload) { | |||
| try { | |||
| yield put(clearOffers()); | |||
| const newQueryString = new URLSearchParams( | |||
| convertQueryStringBackend(payload.payload.queryString) | |||
| convertQueryStringForBackend(payload.payload.queryString) | |||
| ); | |||
| const data = yield call( | |||
| attemptFetchOffers, | |||
| @@ -1,5 +1,5 @@ | |||
| import { all, takeLatest, put } from "@redux-saga/core/effects"; | |||
| import { convertQueryStringBackend } from "../../util/helpers/queryHelpers"; | |||
| import { convertQueryStringForBackend } from "../../util/helpers/queryHelpers"; | |||
| // import { combineQueryStrings } from "../../util/helpers/queryHelpers"; | |||
| import { QUERY_STRING_SET } from "../actions/queryString/queryStringActionConstants"; | |||
| import { setQueryStringRedux } from "../actions/queryString/queryStringActions"; | |||
| @@ -16,7 +16,7 @@ function* setQueryString(payload) { | |||
| // payload.payload, | |||
| // ); | |||
| // } | |||
| const newQueryString = convertQueryStringBackend(payload.payload); | |||
| const newQueryString = convertQueryStringForBackend(payload.payload); | |||
| yield put(setQueryStringRedux(newQueryString)); | |||
| } catch (e) { | |||
| console.log(e); | |||
| @@ -26,3 +26,11 @@ export const selectAppliedStatus = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.isApplied | |||
| ) | |||
| export const selectHeaderString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.headerString | |||
| ) | |||
| export const selectSearchString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.searchString, | |||
| ) | |||
| @@ -1,36 +1,54 @@ | |||
| import { ALL_CATEGORIES, COMMA, SPREAD } from "../../constants/marketplaceHeaderTitle"; | |||
| import { | |||
| initialSize, | |||
| KEY_CATEGORY, | |||
| KEY_LOCATION, | |||
| KEY_NAME, | |||
| KEY_PAGE, | |||
| KEY_SEARCH, | |||
| KEY_SIZE, | |||
| KEY_SORTBY, | |||
| KEY_SORT_DATE, | |||
| KEY_SORT_POPULAR, | |||
| KEY_SUBCATEGORY, | |||
| VALUE_SORTBY_NEW, | |||
| VALUE_SORTBY_OLD, | |||
| VALUE_SORTBY_POPULAR, | |||
| } from "../../constants/queryStringConstants"; | |||
| import { sortEnum } from "../../enums/sortEnum"; | |||
| // import qs from "query-string"; | |||
| export const convertQueryStringFrontend = (queryURL) => { | |||
| export const convertQueryStringForFrontend = (queryURL) => { | |||
| const queryObject = new URLSearchParams(queryURL); | |||
| const queryObjectToReturn = new URLSearchParams(queryURL); | |||
| if (queryObject.has("_des_date")) { | |||
| queryObjectToReturn.delete("_des_date"); | |||
| if (queryObject.get("_des_date") === "true") { | |||
| queryObjectToReturn.append("sortBy", sortEnum.NEW.queryString); | |||
| if (queryObject.has(KEY_SORT_DATE)) { | |||
| queryObjectToReturn.delete(KEY_SORT_DATE); | |||
| if (queryObject.get(KEY_SORT_DATE) === "true") { | |||
| queryObjectToReturn.append(KEY_SORTBY, sortEnum.NEW.queryString); | |||
| } else { | |||
| queryObjectToReturn.append("sortBy", sortEnum.OLD.queryString); | |||
| queryObjectToReturn.append(KEY_SORTBY, sortEnum.OLD.queryString); | |||
| } | |||
| } | |||
| if (queryObject.has("oname")) { | |||
| queryObjectToReturn.delete("oname"); | |||
| queryObjectToReturn.append("search", queryObject.get("oname")); | |||
| if (queryObject.has(KEY_NAME)) { | |||
| queryObjectToReturn.delete(KEY_NAME); | |||
| queryObjectToReturn.append(KEY_SEARCH, queryObject.get(KEY_NAME)); | |||
| } | |||
| if (queryObject.has("_des_popular")) { | |||
| queryObjectToReturn.delete("_des_popular"); | |||
| queryObjectToReturn.append("sortBy", sortEnum.POPULAR.queryString); | |||
| if (queryObject.has(KEY_SORT_POPULAR)) { | |||
| queryObjectToReturn.delete(KEY_SORT_POPULAR); | |||
| queryObjectToReturn.append(KEY_SORTBY, sortEnum.POPULAR.queryString); | |||
| } | |||
| if (queryObject.has("size")) { | |||
| queryObjectToReturn.delete("size"); | |||
| if (queryObject.has(KEY_SIZE)) { | |||
| queryObjectToReturn.delete(KEY_SIZE); | |||
| } | |||
| if (queryObject.has("page")) { | |||
| if (queryObject.get("page") === "1") { | |||
| queryObjectToReturn.delete("page"); | |||
| if (queryObject.has(KEY_PAGE)) { | |||
| if (queryObject.get(KEY_PAGE) === "1") { | |||
| queryObjectToReturn.delete(KEY_PAGE); | |||
| queryObjectToReturn.delete(KEY_SIZE); | |||
| return queryObjectToReturn.toString(); | |||
| } else { | |||
| queryObjectToReturn.delete("page"); | |||
| queryObjectToReturn.delete(KEY_PAGE); | |||
| return ( | |||
| queryObjectToReturn.toString() + "&page=" + queryObject.get("page") | |||
| queryObjectToReturn.toString() + "&page=" + queryObject.get(KEY_PAGE) | |||
| ); | |||
| } | |||
| } | |||
| @@ -60,64 +78,175 @@ export const combineQueryStrings = (firstQuery, secondQuery) => { | |||
| return thirdQueryObject.toString(); | |||
| }; | |||
| export const convertQueryStringBackend = (queryURL) => { | |||
| export const convertQueryStringForBackend = (queryURL) => { | |||
| const queryObject = new URLSearchParams(queryURL); | |||
| const newQueryObject = new URLSearchParams(); | |||
| if (queryObject.has("category")) { | |||
| if (queryObject.has(KEY_CATEGORY)) { | |||
| newQueryObject.append( | |||
| "category", | |||
| queryObject.getAll("category")[queryObject.getAll("category").length - 1] | |||
| KEY_CATEGORY, | |||
| queryObject.getAll(KEY_CATEGORY)[ | |||
| queryObject.getAll(KEY_CATEGORY).length - 1 | |||
| ] | |||
| ); | |||
| } | |||
| if (queryObject.has("subcategory")) { | |||
| if (queryObject.has(KEY_SUBCATEGORY)) { | |||
| newQueryObject.append( | |||
| "subcategory", | |||
| queryObject.getAll("subcategory")[ | |||
| queryObject.getAll("subcategory").length - 1 | |||
| KEY_SUBCATEGORY, | |||
| queryObject.getAll(KEY_SUBCATEGORY)[ | |||
| queryObject.getAll(KEY_SUBCATEGORY).length - 1 | |||
| ] | |||
| ); | |||
| } | |||
| if (queryObject.has("search")) { | |||
| if (queryObject.has(KEY_SEARCH) && queryObject.get(KEY_SEARCH)?.length > 0) { | |||
| newQueryObject.append( | |||
| "oname", | |||
| queryObject.getAll("search")[queryObject.getAll("search").length - 1] | |||
| KEY_NAME, | |||
| queryObject.getAll(KEY_SEARCH)[queryObject.getAll(KEY_SEARCH).length - 1] | |||
| ); | |||
| } | |||
| if (queryObject.has("oname")) { | |||
| if (queryObject.has(KEY_NAME)) { | |||
| newQueryObject.append( | |||
| "oname", | |||
| queryObject.getAll("oname")[queryObject.getAll("oname").length - 1] | |||
| KEY_NAME, | |||
| queryObject.getAll(KEY_NAME)[queryObject.getAll(KEY_NAME).length - 1] | |||
| ); | |||
| } | |||
| if (queryObject.has("location")) { | |||
| const arrayOfLocations = queryObject.getAll("location"); | |||
| if (queryObject.has(KEY_LOCATION)) { | |||
| const arrayOfLocations = queryObject.getAll(KEY_LOCATION); | |||
| arrayOfLocations.forEach((item) => { | |||
| newQueryObject.append("location", item); | |||
| newQueryObject.append(KEY_LOCATION, item); | |||
| }); | |||
| } | |||
| if (queryObject.has("sortBy")) { | |||
| newQueryObject.delete("sortBy"); | |||
| if (queryObject.get("sortBy") === "newest") { | |||
| newQueryObject.append("_des_date", "true"); | |||
| if (queryObject.has(KEY_SORTBY)) { | |||
| newQueryObject.delete(KEY_SORTBY); | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_NEW) { | |||
| newQueryObject.append(KEY_SORT_DATE, "true"); | |||
| } | |||
| if (queryObject.get("sortBy") === "oldest") { | |||
| newQueryObject.append("_des_date", "false"); | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_OLD) { | |||
| newQueryObject.append(KEY_SORT_DATE, "false"); | |||
| } | |||
| if (queryObject.get("sortBy") === "popular") { | |||
| newQueryObject.append("_des_popular", "true"); | |||
| if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_POPULAR) { | |||
| newQueryObject.append(KEY_SORT_POPULAR, "true"); | |||
| } | |||
| } | |||
| if (queryObject.has("_des_date")) { | |||
| newQueryObject.append("_des_date", queryObject.get("_des_date")); | |||
| if (queryObject.has(KEY_SORT_DATE)) { | |||
| newQueryObject.append(KEY_SORT_DATE, queryObject.get(KEY_SORT_DATE)); | |||
| } | |||
| if (queryObject.has("_des_popular")) { | |||
| newQueryObject.append("_des_popular", queryObject.get("_des_popular")); | |||
| if (queryObject.has(KEY_SORT_POPULAR)) { | |||
| newQueryObject.append(KEY_SORT_POPULAR, queryObject.get(KEY_SORT_POPULAR)); | |||
| } | |||
| newQueryObject.append("size", "10"); | |||
| if (!queryObject.has("page")) { | |||
| newQueryObject.append("page", "1"); | |||
| newQueryObject.append(KEY_SIZE, initialSize); | |||
| if (!queryObject.has(KEY_PAGE)) { | |||
| newQueryObject.append(KEY_PAGE, "1"); | |||
| } else { | |||
| newQueryObject.append("page", queryObject.get("page")); | |||
| newQueryObject.append(KEY_PAGE, queryObject.get(KEY_PAGE)); | |||
| } | |||
| return newQueryObject.toString(); | |||
| }; | |||
| export const getQueryObjectHelper = (queryString) => { | |||
| let newObject = {}; | |||
| const queryObject = new URLSearchParams(queryString); | |||
| if (queryObject.has(KEY_CATEGORY)) { | |||
| newObject[KEY_CATEGORY] = queryObject.get(KEY_CATEGORY); | |||
| } | |||
| if (queryObject.has(KEY_SUBCATEGORY)) { | |||
| newObject[KEY_SUBCATEGORY] = queryObject.get(KEY_SUBCATEGORY); | |||
| } | |||
| if (queryObject.has(KEY_SEARCH)) { | |||
| newObject[KEY_SEARCH] = queryObject.get(KEY_SEARCH); | |||
| } | |||
| if (queryObject.has(KEY_NAME)) { | |||
| newObject[KEY_NAME] = queryObject.get(KEY_NAME); | |||
| } | |||
| if (queryObject.has(KEY_LOCATION)) { | |||
| const arrayOfLocations = queryObject.getAll(KEY_LOCATION); | |||
| newObject[KEY_LOCATION] = []; | |||
| arrayOfLocations.forEach((item) => { | |||
| newObject[KEY_LOCATION].push(item); | |||
| }); | |||
| } | |||
| if (queryObject.has(KEY_SORTBY)) { | |||
| newObject[KEY_SORTBY] = queryObject.get(KEY_SORTBY); | |||
| } | |||
| if (queryObject.has(KEY_SORT_DATE)) { | |||
| newObject[KEY_SORT_DATE] = queryObject.get(KEY_SORT_DATE); | |||
| } | |||
| if (queryObject.has(KEY_SORT_POPULAR)) { | |||
| newObject[KEY_SORT_POPULAR] = queryObject.get(KEY_SORT_POPULAR); | |||
| } | |||
| if (queryObject.has(KEY_PAGE)) { | |||
| newObject[KEY_PAGE] = queryObject.get(KEY_PAGE); | |||
| } | |||
| if (queryObject.has(KEY_SIZE)) { | |||
| newObject[KEY_SIZE] = queryObject.get(KEY_SIZE); | |||
| } | |||
| return newObject; | |||
| }; | |||
| export const makeHeaderStringHelper = (filters) => { | |||
| let headerStringLocal = ALL_CATEGORIES; | |||
| // Adding category to header string | |||
| if (filters.category.selectedCategory?.name) { | |||
| headerStringLocal = filters.category.selectedCategory?.name; | |||
| // Adding subcategories to header string | |||
| if (filters.subcategory.selectedSubcategory?.name) { | |||
| headerStringLocal += `${SPREAD}${filters.subcategory.selectedSubcategory.name}`; | |||
| } | |||
| } | |||
| // Adding locations to header string | |||
| if ( | |||
| filters.locations.selectedLocations && | |||
| filters.locations.selectedLocations?.length > 0 | |||
| ) { | |||
| headerStringLocal += SPREAD; | |||
| filters.locations.selectedLocations.forEach((location, index) => { | |||
| // Checking if item is last | |||
| if (index + 1 === filters.locations.selectedLocations.length) { | |||
| headerStringLocal += location.city; | |||
| } else { | |||
| headerStringLocal += location.city + COMMA; | |||
| } | |||
| }); | |||
| } | |||
| return headerStringLocal; | |||
| } | |||
| export const makeQueryStringHelper = (filters, paging, search, sorting) => { | |||
| const newQueryString = new URLSearchParams(); | |||
| if (filters.category.selectedCategoryLocally?.name) { | |||
| newQueryString.append( | |||
| KEY_CATEGORY, | |||
| filters.category.selectedCategoryLocally.name | |||
| ); | |||
| } | |||
| if (filters.subcategory.selectedSubcategoryLocally?.name) { | |||
| newQueryString.append( | |||
| KEY_SUBCATEGORY, | |||
| filters.subcategory.selectedSubcategoryLocally.name | |||
| ); | |||
| } | |||
| if (filters.locations.selectedLocationsLocally?.length > 0) { | |||
| filters.locations.selectedLocationsLocally.forEach((location) => | |||
| newQueryString.append(KEY_LOCATION, location?.city) | |||
| ); | |||
| } | |||
| if (sorting.selectedSortOption?.value) { | |||
| if (sorting.selectedSortOption?.value === sortEnum.NEW.value) { | |||
| newQueryString.append(KEY_SORTBY, VALUE_SORTBY_NEW); | |||
| } | |||
| if (sorting.selectedSortOption?.value === sortEnum.OLD.value) { | |||
| newQueryString.append(KEY_SORTBY, VALUE_SORTBY_OLD); | |||
| } | |||
| if (sorting.selectedSortOption?.value === sortEnum.POPULAR.value) { | |||
| newQueryString.append(KEY_SORTBY, VALUE_SORTBY_POPULAR); | |||
| } | |||
| } | |||
| if (paging.currentPage !== 1) { | |||
| newQueryString.append(KEY_PAGE, paging.currentPage); | |||
| } | |||
| newQueryString.append(KEY_SEARCH, search.searchString ?? ""); | |||
| return newQueryString; | |||
| } | |||