Преглед изворни кода

Finished feature 549

feature/587
Djordje Mitrovic пре 3 година
родитељ
комит
182302cf35
53 измењених фајлова са 1327 додато и 1085 уклоњено
  1. 5
    0
      package-lock.json
  2. 1
    0
      package.json
  3. 2
    2
      src/App.js
  4. 1
    0
      src/assets/styles/_base.scss
  5. 7
    7
      src/components/Cards/FilterCard/Choser/CategoryChoser/CategoryChoser.js
  6. 3
    7
      src/components/Cards/FilterCard/Choser/LocationChoser/LocationChoser.js
  7. 21
    21
      src/components/Cards/FilterCard/Choser/SubcategoryChoser/SubcategoryChoser.js
  8. 8
    8
      src/components/Cards/FilterCard/FilterCard.js
  9. 1
    1
      src/components/Cards/FilterCard/FilterCard.styled.js
  10. 1
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  11. 2
    3
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  12. 8
    6
      src/components/Cards/FilterCard/FilterFooter/FilterFooter.js
  13. 1
    1
      src/components/Cards/FilterCard/FilterHeader/FilterHeader.js
  14. 1
    1
      src/components/ChatColumn/ChatColumn.js
  15. 46
    49
      src/components/Header/Header.js
  16. 0
    23
      src/components/Header/Header.styled.js
  17. 17
    13
      src/components/Icon/IconWithNumber/IconWithNumber.js
  18. 91
    127
      src/components/MarketPlace/Header/Header.js
  19. 19
    2
      src/components/MarketPlace/MarketPlace.js
  20. 4
    3
      src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.js
  21. 8
    0
      src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.styled.js
  22. 28
    12
      src/components/MarketPlace/Offers/Offers.js
  23. 27
    0
      src/components/MarketPlace/Offers/Offers.styled.js
  24. 1
    0
      src/components/Paging/Paging.js
  25. 3
    0
      src/constants/queryStringConstants.js
  26. 0
    182
      src/hooks/useFilters.js
  27. 0
    253
      src/hooks/useOffers.js
  28. 66
    0
      src/hooks/useOffers/useCategoryFilter.js
  29. 50
    0
      src/hooks/useOffers/useFilters.js
  30. 54
    0
      src/hooks/useOffers/useLocationsFilter.js
  31. 101
    0
      src/hooks/useOffers/useMyOffers.js
  32. 134
    0
      src/hooks/useOffers/useOffers.js
  33. 38
    0
      src/hooks/useOffers/usePaging.js
  34. 60
    0
      src/hooks/useOffers/useQueryString.js
  35. 46
    0
      src/hooks/useOffers/useSearch.js
  36. 65
    0
      src/hooks/useOffers/useSorting.js
  37. 55
    0
      src/hooks/useOffers/useSubcategoryFilter.js
  38. 0
    174
      src/hooks/useQueryString.js
  39. 11
    11
      src/hooks/useSearch.js
  40. 36
    0
      src/hooks/useSkeleton.js
  41. 0
    86
      src/hooks/useSorting.js
  42. 19
    18
      src/pages/HomePage/HomePageMUI.js
  43. 38
    3
      src/pages/MyOffers/MyOffers.js
  44. 1
    1
      src/request/index.js
  45. 2
    0
      src/store/actions/filters/filtersActionConstants.js
  46. 9
    1
      src/store/actions/filters/filtersActions.js
  47. 18
    13
      src/store/index.js
  48. 24
    0
      src/store/reducers/filters/filtersReducer.js
  49. 1
    1
      src/store/reducers/queryString/queryStringReducer.js
  50. 2
    2
      src/store/saga/offersSaga.js
  51. 2
    2
      src/store/saga/queryStringSaga.js
  52. 8
    0
      src/store/selectors/filtersSelectors.js
  53. 181
    52
      src/util/helpers/queryHelpers.js

+ 5
- 0
package-lock.json Прегледај датотеку

@@ -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",

+ 1
- 0
package.json Прегледај датотеку

@@ -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",

+ 2
- 2
src/App.js Прегледај датотеку

@@ -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>
);

+ 1
- 0
src/assets/styles/_base.scss Прегледај датотеку

@@ -3,6 +3,7 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-anchor: none;
background-color: #F1F1F1;
}

* {

+ 7
- 7
src/components/Cards/FilterCard/Choser/CategoryChoser/CategoryChoser.js Прегледај датотеку

@@ -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}
/>
);

+ 3
- 7
src/components/Cards/FilterCard/Choser/LocationChoser/LocationChoser.js Прегледај датотеку

@@ -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}
/>
);
};

+ 21
- 21
src/components/Cards/FilterCard/Choser/SubcategoryChoser/SubcategoryChoser.js Прегледај датотеку

@@ -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}
/>
);
};

+ 8
- 8
src/components/Cards/FilterCard/FilterCard.js Прегледај датотеку

@@ -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 = {

+ 1
- 1
src/components/Cards/FilterCard/FilterCard.styled.js Прегледај датотеку

@@ -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;

+ 1
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js Прегледај датотеку

@@ -64,6 +64,7 @@ const FilterCheckboxDropdown = (props) => {
searchPlaceholder={props.searchPlaceholder}
isOpened={isOpened}
setIsOpened={setIsOpened}
setItemsSelected={props.setItemsSelected}
>
{dataToShow.map((item) => {
return (

+ 2
- 3
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js Прегледај датотеку

@@ -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={{

+ 8
- 6
src/components/Cards/FilterCard/FilterFooter/FilterFooter.js Прегледај датотеку

@@ -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 = {

+ 1
- 1
src/components/Cards/FilterCard/FilterHeader/FilterHeader.js Прегледај датотеку

@@ -8,7 +8,7 @@ const FilterHeader = (props) => {
const filters = props.filters;
const { t } = useTranslation();
const clearFilters = () => {
filters.clearFilters();
filters.clear();
};
return (
<FilterHeaderContainer>

+ 1
- 1
src/components/ChatColumn/ChatColumn.js Прегледај датотеку

@@ -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}>

+ 46
- 49
src/components/Header/Header.js Прегледај датотеку

@@ -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} />
)}

+ 0
- 23
src/components/Header/Header.styled.js Прегледај датотеку

@@ -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)``;

+ 17
- 13
src/components/Icon/IconWithNumber/IconWithNumber.js Прегледај датотеку

@@ -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;

+ 91
- 127
src/components/MarketPlace/Header/Header.js Прегледај датотеку

@@ -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,

+ 19
- 2
src/components/MarketPlace/MarketPlace.js Прегледај датотеку

@@ -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;

+ 4
- 3
src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.js Прегледај датотеку

@@ -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;

+ 8
- 0
src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.styled.js Прегледај датотеку

@@ -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;
}
`

+ 28
- 12
src/components/MarketPlace/Offers/Offers.js Прегледај датотеку

@@ -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 = {

+ 27
- 0
src/components/MarketPlace/Offers/Offers.styled.js Прегледај датотеку

@@ -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};
`;

+ 1
- 0
src/components/Paging/Paging.js Прегледај датотеку

@@ -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(() => {});


+ 3
- 0
src/constants/queryStringConstants.js Прегледај датотеку

@@ -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";

+ 0
- 182
src/hooks/useFilters.js Прегледај датотеку

@@ -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;

+ 0
- 253
src/hooks/useOffers.js Прегледај датотеку

@@ -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;

+ 66
- 0
src/hooks/useOffers/useCategoryFilter.js Прегледај датотеку

@@ -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;

+ 50
- 0
src/hooks/useOffers/useFilters.js Прегледај датотеку

@@ -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;

+ 54
- 0
src/hooks/useOffers/useLocationsFilter.js Прегледај датотеку

@@ -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;

+ 101
- 0
src/hooks/useOffers/useMyOffers.js Прегледај датотеку

@@ -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;

+ 134
- 0
src/hooks/useOffers/useOffers.js Прегледај датотеку

@@ -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;

+ 38
- 0
src/hooks/useOffers/usePaging.js Прегледај датотеку

@@ -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;

+ 60
- 0
src/hooks/useOffers/useQueryString.js Прегледај датотеку

@@ -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;

+ 46
- 0
src/hooks/useOffers/useSearch.js Прегледај датотеку

@@ -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;

+ 65
- 0
src/hooks/useOffers/useSorting.js Прегледај датотеку

@@ -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;

+ 55
- 0
src/hooks/useOffers/useSubcategoryFilter.js Прегледај датотеку

@@ -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;

+ 0
- 174
src/hooks/useQueryString.js Прегледај датотеку

@@ -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,
};
};

+ 11
- 11
src/hooks/useSearch.js Прегледај датотеку

@@ -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 {

+ 36
- 0
src/hooks/useSkeleton.js Прегледај датотеку

@@ -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;

+ 0
- 86
src/hooks/useSorting.js Прегледај датотеку

@@ -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;

+ 19
- 18
src/pages/HomePage/HomePageMUI.js Прегледај датотеку

@@ -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}
/>
}
/>

+ 38
- 3
src/pages/MyOffers/MyOffers.js Прегледај датотеку

@@ -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>
);

+ 1
- 1
src/request/index.js Прегледај датотеку

@@ -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",

+ 2
- 0
src/store/actions/filters/filtersActionConstants.js Прегледај датотеку

@@ -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");

+ 9
- 1
src/store/actions/filters/filtersActions.js Прегледај датотеку

@@ -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
})

+ 18
- 13
src/store/index.js Прегледај датотеку

@@ -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);


+ 24
- 0
src/store/reducers/filters/filtersReducer.js Прегледај датотеку

@@ -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
}
}
}

+ 1
- 1
src/store/reducers/queryString/queryStringReducer.js Прегледај датотеку

@@ -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(

+ 2
- 2
src/store/saga/offersSaga.js Прегледај датотеку

@@ -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,

+ 2
- 2
src/store/saga/queryStringSaga.js Прегледај датотеку

@@ -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);

+ 8
- 0
src/store/selectors/filtersSelectors.js Прегледај датотеку

@@ -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,
)

+ 181
- 52
src/util/helpers/queryHelpers.js Прегледај датотеку

@@ -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;
}



Loading…
Откажи
Сачувај