Переглянути джерело

Finished market place

feature/code-cleanup-joca
Djordje Mitrovic 3 роки тому
джерело
коміт
36a699d7d5
61 змінених файлів з 2119 додано та 790 видалено
  1. 15
    15
      package-lock.json
  2. 20
    22
      src/App.js
  3. 5
    4
      src/AppRoutes.js
  4. 3
    0
      src/assets/images/svg/filter.svg
  5. 5
    0
      src/assets/images/svg/log-out.svg
  6. 1
    1
      src/assets/styles/_base.scss
  7. 33
    7
      src/components/Cards/FilterCard/FilterCard.js
  8. 25
    5
      src/components/Cards/FilterCard/FilterCard.styled.js
  9. 22
    7
      src/components/Cards/OfferCard/OfferCard.js
  10. 88
    11
      src/components/Cards/OfferCard/OfferCard.styled.js
  11. 365
    188
      src/components/Header/Header.js
  12. 127
    14
      src/components/Header/Header.styled.js
  13. 2
    1
      src/components/Icon/IconWithNumber/IconWithNumber.js
  14. 20
    10
      src/components/MarketPlace/Header/Header.js
  15. 82
    45
      src/components/MarketPlace/Header/Header.styled.js
  16. 2
    2
      src/components/MarketPlace/MarketPlace.styled.js
  17. 83
    46
      src/components/MarketPlace/Offers/Offers.js
  18. 3
    0
      src/components/MarketPlace/Offers/Offers.styled.js
  19. 76
    0
      src/components/Paging/Paging.js
  20. 137
    0
      src/components/Paging/Paging.styled.js
  21. 43
    21
      src/components/Popovers/HeaderPopover/HeaderPopover.js
  22. 15
    1
      src/components/Popovers/HeaderPopover/HeaderPopover.styled.js
  23. 20
    37
      src/components/Popovers/MyMessages/MyMessages.js
  24. 64
    19
      src/components/Popovers/MyPosts/MyPosts.js
  25. 50
    13
      src/components/Popovers/MyProfile/MyProfile.js
  26. 3
    0
      src/components/Popovers/MyProfile/MyProfile.styled.js
  27. 4
    0
      src/components/Select/Select.styled.js
  28. 0
    1
      src/components/TextFields/TextField/TextField.js
  29. 150
    95
      src/hooks/useFilters.js
  30. 8
    0
      src/hooks/usePaging.js
  31. 182
    0
      src/hooks/useQueryString.js
  32. 30
    0
      src/hooks/useScreenDimensions.js
  33. 12
    8
      src/hooks/useSearch.js
  34. 42
    72
      src/hooks/useSorting.js
  35. 3
    0
      src/i18n/resources/rs.js
  36. 0
    44
      src/pages/HomePage/HomePageMUI.js
  37. 2
    2
      src/pages/RegisterPages/Register/Register.styled.js
  38. 2
    1
      src/request/apiEndpoints.js
  39. 3
    0
      src/request/chatRequest.js
  40. 5
    2
      src/request/offersRequest.js
  41. 2
    0
      src/store/actions/chat/chatActionConstants.js
  42. 5
    1
      src/store/actions/chat/chatActions.js
  43. 2
    1
      src/store/actions/login/loginActions.js
  44. 5
    1
      src/store/actions/offers/offersActionConstants.js
  45. 55
    30
      src/store/actions/offers/offersActions.js
  46. 2
    0
      src/store/actions/queryString/queryStringActionConstants.js
  47. 10
    0
      src/store/actions/queryString/queryStringActions.js
  48. 0
    11
      src/store/reducers/filters/filtersReducer.js
  49. 3
    1
      src/store/reducers/index.js
  50. 18
    0
      src/store/reducers/offers/offersReducer.js
  51. 20
    0
      src/store/reducers/queryString/queryStringReducer.js
  52. 13
    3
      src/store/saga/chatSaga.js
  53. 3
    1
      src/store/saga/index.js
  54. 7
    3
      src/store/saga/loginSaga.js
  55. 57
    23
      src/store/saga/offersSaga.js
  56. 30
    0
      src/store/saga/queryStringSaga.js
  57. 0
    4
      src/store/selectors/filtersSelectors.js
  58. 8
    0
      src/store/selectors/offersSelectors.js
  59. 8
    0
      src/store/selectors/queryStringSelectors.js
  60. 0
    2
      src/util/helpers/authScopeHelpers.js
  61. 119
    15
      src/util/helpers/queryHelpers.js

+ 15
- 15
package-lock.json Переглянути файл

@@ -3942,32 +3942,32 @@
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
"integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
"integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
"requires": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.18.6"
}
},
"@babel/helper-module-imports": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
"integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
"requires": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.18.6"
}
},
"@babel/helper-validator-identifier": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g=="
},
"@babel/types": {
"version": "7.18.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz",
"integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==",
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.9.tgz",
"integrity": "sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==",
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
"@babel/helper-validator-identifier": "^7.18.6",
"to-fast-properties": "^2.0.0"
}
}

+ 20
- 22
src/App.js Переглянути файл

@@ -1,28 +1,26 @@
import React from 'react';
import { Router } from 'react-router-dom';
import { Helmet } from 'react-helmet-async';
import i18next from 'i18next';
import history from './store/utils/history';
import AppRoutes from './AppRoutes';
import Header from './components/Header/Header';
import { StyledEngineProvider } from '@mui/material';
import GlobalStyle from './components/Styles/globalStyles';
import React from "react";
import { Router } from "react-router-dom";
import { Helmet } from "react-helmet-async";
import i18next from "i18next";
import history from "./store/utils/history";
import AppRoutes from "./AppRoutes";
import Header from "./components/Header/Header";
import { StyledEngineProvider } from "@mui/material";
import GlobalStyle from "./components/Styles/globalStyles";

const App = () => {
return (
<>
<Router history={history}>
<Helmet>
<title>{i18next.t("app.title")}</title>
</Helmet>
<Header/>
<StyledEngineProvider injectFirst>
<GlobalStyle />
<AppRoutes />
</StyledEngineProvider>
{/* </main> */}
</Router>
</>
<Router history={history}>
<Helmet>
<title>{i18next.t("app.title")}</title>
</Helmet>
<StyledEngineProvider injectFirst>
<Header />
<GlobalStyle />
<AppRoutes />
</StyledEngineProvider>
{/* </main> */}
</Router>
);
};


+ 5
- 4
src/AppRoutes.js Переглянути файл

@@ -19,7 +19,7 @@ import HomePage from './pages/HomePage/HomePageMUI';
import NotFoundPage from './pages/ErrorPages/NotFoundPage';
import ErrorPage from './pages/ErrorPages/ErrorPage';
import ForgotPasswordPage from './pages/ForgotPasswordPage/ForgotPasswordPage';
import PrivateRoute from './components/Router/PrivateRoute';
// import PrivateRoute from './components/Router/PrivateRoute';
import MailSent from './pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent';
import Register from './pages/RegisterPages/Register/Register';
import RegisterSuccessful from './pages/RegisterPages/RegisterSuccessful.js/RegisterSuccessful';
@@ -40,12 +40,13 @@ const AppRoutes = () => {
<Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} />
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage}/>
<Route path={CREATE_OFFER_PAGE} component={CreateOffer}/>
<PrivateRoute
<Route path={HOME_PAGE} component={HomePage} />

{/* <PrivateRoute
exact
path={HOME_PAGE}
component={HomePage}
/>
/> */}
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>
)};

+ 3
- 0
src/assets/images/svg/filter.svg Переглянути файл

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 3H2L10 12.46V19L14 21V12.46L22 3Z" stroke="#5A3984" stroke-width="1.28" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 5
- 0
src/assets/images/svg/log-out.svg Переглянути файл

@@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75 15.75H3.75C3.35218 15.75 2.97064 15.592 2.68934 15.3107C2.40804 15.0294 2.25 14.6478 2.25 14.25V3.75C2.25 3.35218 2.40804 2.97064 2.68934 2.68934C2.97064 2.40804 3.35218 2.25 3.75 2.25H6.75" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 12.75L15.75 9L12 5.25" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.75 9H6.75" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 1
- 1
src/assets/styles/_base.scss Переглянути файл

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

* {

+ 33
- 7
src/components/Cards/FilterCard/FilterCard.js Переглянути файл

@@ -19,7 +19,7 @@ import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import useFilters from "../../../hooks/useFilters";

const FilterCard = () => {
const FilterCard = (props) => {
const { t } = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);
@@ -32,26 +32,27 @@ const FilterCard = () => {
} else {
setIsDisabled(false);
}
}, [filters.selectedCategory])
}, [filters.selectedCategory]);

const handleSelectCategory = (category) => {
filters.setSelectedCategory(category);
filters.setSelectedSubcategory();
}
};

const handleOpen = () => {
setIsOpened(prevState => !prevState);
}
setIsOpened((prevState) => !prevState);
};

const handleFilters = () => {
filters.applyFilters();
if (props.closeResponsive) props.closeResponsive();
};
const clearFilters = () => {
filters.clearFilters();
};

return (
<FilterCardContainer>
<FilterCardContainer responsiveOpen={props.responsiveOpen} responsive={props.responsive}>
<Header>
<Title>{t("filters.title")}</Title>
<Link
@@ -116,7 +117,24 @@ const FilterCard = () => {
/>
</ContentContainer>

<Footer>
<Footer responsiveOpen={props.responsiveOpen}>
{props.responsiveOpen && (
<PrimaryButton
variant="outlined"
fullWidth
onClick={props.closeResponsive}
textcolor={selectedTheme.primaryPurple}
font="Open Sans"
style={{
fontWeight: "600",
fontSize: "12px",
border: "0",
textAlign: "center"
}}
>
ZATVORI
</PrimaryButton>
)}
<PrimaryButton
variant="outlined"
fullWidth
@@ -139,6 +157,14 @@ const FilterCard = () => {
FilterCard.propTypes = {
children: PropTypes.node,
filters: PropTypes.any,
responsive: PropTypes.bool,
responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func,
};

FilterCard.defaultProps = {
responsive: false,
responsiveOpen: false,
}

export default FilterCard;

+ 25
- 5
src/components/Cards/FilterCard/FilterCard.styled.js Переглянути файл

@@ -4,24 +4,38 @@ import selectedTheme from "../../../themes";

export const FilterCardContainer = styled(Box)`
position: fixed;
box-sizing: border-box;
border-radius: 0;
border-top-right-radius: 4px;
height: calc(100% - 90px);
padding: 36px;
background-color: white;
width: calc(100% / 12 * 2.4);
width: calc(100% / 12 * 3.5);
left: 0;
display: flex;
max-width: 360px;
display: ${(props) => (props.responsive && !props.responsiveOpen ? "none" : "flex")};
flex-direction: column;
justify-content: space-between;
background-color: white;
min-width: fit-content;
min-width: 285px !important;
z-index: 9;
margin-top: -24px;
transition: all ease-in-out .36s;
transition: all ease-in-out 0.36s;
@media (max-width: 900px) {
margin-left: -400px;
transition: all ease-in-out .36s;
${(props) =>
props.responsiveOpen
? `
display: "flex";
margin-left: 0;
max-width: 100vw;
width: 100vw;
bottom: 0;
height: calc(100% - 50px);
` : "display: none"};
transition: all ease-in-out 0.36s;

}
@media (max-width: 600px) {
margin-top: -14px;
@@ -45,6 +59,12 @@ export const Header = styled(Box)`

export const Footer = styled(Box)`
position: "sticky";
${(props) =>
props.responsiveOpen &&
`
flex-direction: row;
display: flex;
justify-content: space-around;`}
bottom: 0;
& div button {
height: 48px;
@@ -70,5 +90,5 @@ export const ContentContainer = styled(Box)`
}
scrollbar-width: thin;
scrollbar-color: #ddd;
${() => window.scrollbars.visible && `padding-right: 15px;`}
${() => window.scrollbars.visible && `padding-right: 15px`};
`;

+ 22
- 7
src/components/Cards/OfferCard/OfferCard.js Переглянути файл

@@ -4,6 +4,7 @@ import {
CheckButton,
DetailIcon,
DetailText,
EyeIcon,
Line,
MessageIcon,
OfferAuthor,
@@ -14,21 +15,31 @@ import {
OfferDescriptionText,
OfferDescriptionTitle,
OfferDetails,
OfferFlexContainer,
OfferImage,
OfferImageContainer,
OfferInfo,
OfferLocation,
OfferTitle,
OfferTitleAboveImage,
OfferViews,
} from "./OfferCard.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import selectedTheme from "../../../themes";

const OfferCard = (props) => {
return (
<OfferCardContainer sponsored={props.offer.pinned.toString()} halfwidth={props.halfwidth ? 1 : 0}>
<OfferImage src={props.offer.images[0]}></OfferImage>
<React.Fragment>
<OfferCardContainer
sponsored={props.offer.pinned.toString()}
halfwidth={props.halfwidth ? 1 : 0}
>
<OfferTitleAboveImage>{props.offer.name}</OfferTitleAboveImage>
<OfferFlexContainer>
<OfferImageContainer>
<OfferImage src={props.offer.images[0]}></OfferImage>
</OfferImageContainer>
<OfferInfo>
<OfferTitle>{props.offer.name}</OfferTitle>
<OfferAuthor>
@@ -44,7 +55,7 @@ const OfferCard = (props) => {
</OfferCategory>
<OfferViews>
<DetailIcon color="black" component="span" size="16px">
<Eye width={"12px"} height={"11px"} />
<EyeIcon />
</DetailIcon>
<DetailText>{props.offer.views.viewers.length}</DetailText>
</OfferViews>
@@ -52,17 +63,19 @@ const OfferCard = (props) => {
</OfferInfo>
{!props.halfwidth ? (
<React.Fragment>
<Line/>
<Line />
<OfferDescription>
<OfferDescriptionTitle>Opis:</OfferDescriptionTitle>
<OfferDescriptionText>{props.offer.description}</OfferDescriptionText>
<OfferDescriptionText>
{props.offer.description}
</OfferDescriptionText>
</OfferDescription>

<CheckButton
variant={props.sponsored ? "contained" : "outlined"}
buttoncolor={selectedTheme.primaryPurple}
textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple}
style={{fontWeight: "600"}}
style={{ fontWeight: "600" }}
>
Pogledaj proizvod
</CheckButton>
@@ -84,7 +97,9 @@ const OfferCard = (props) => {
{props.quantity}
{props.package}
{props.numberOfViews} */}
</OfferFlexContainer>
</OfferCardContainer>
</React.Fragment>
);
};


+ 88
- 11
src/components/Cards/OfferCard/OfferCard.styled.js Переглянути файл

@@ -4,27 +4,50 @@ import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";

export const OfferCardContainer = styled(Container)`
display: flex;
flex-direction: row;
flex-direction: column;
width: ${(props) => (!props.halfwidth ? "100%" : "49%")};
box-sizing: border-box;
margin: 10px 0;
background-color: ${(props) =>
props.sponsored === 'true' ? selectedTheme.backgroundSponsoredColor : "white"};
props.sponsored === "true"
? selectedTheme.backgroundSponsoredColor
: "white"};
border-radius: 4px;
${(props) =>
props.sponsored === 'true' &&
props.sponsored === "true" &&
`border: 1px solid ${selectedTheme.borderSponsoredColor};`}
padding: 16px;
max-width: 2000px;
height: 180px;
position: relative;
@media (max-width: 550px) {
height: 184px;
padding: 18px;
padding-top: 12px;
}
`;
export const OfferFlexContainer = styled(Container)`
display: flex;
flex-direction: row;
margin: 0;
padding: 0;
max-height: 184px;
`;
export const OfferImage = styled.img`
max-width: 144px;
max-height: 144px;
width: 144px;
height: 144px;
@media (max-width: 600px) {
max-width: 108px;
max-height: 108px;
width: 108px;
height: 108px;
}
`;
export const OfferInfo = styled(Box)`
display: flex;
@@ -39,6 +62,10 @@ export const OfferTitle = styled(Typography)`
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 24px;
@media (max-width: 550px) {
font-size: 18px;
display: none;
}
`;
export const OfferAuthor = styled(Box)`
display: flex;
@@ -49,11 +76,16 @@ export const OfferAuthorName = styled(Typography)`
font-family: "Open Sans";
line-height: 22px;
font-size: 16px;
color: ${selectedTheme.primaryDarkText};
color: ${selectedTheme.primaryText};
@media (max-width: 600px) {
font-size: 14px;
position: relative;
left: -1px;
}
`;
export const OfferLocation = styled(Typography)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
color: ${selectedTheme.primaryDarkText};
line-height: 16px;
font-size: 12px;
`;
@@ -63,15 +95,17 @@ export const OfferDetails = styled(Box)`
flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")};
justify-content: start;
gap: 1rem;
@media (max-width: 650px) {
flex-direction: column;
justify-content: center;
gap: 0;
}
`;
export const OfferCategory = styled(Box)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
font-size: 12px;
@media (max-width: 1000px) {
display: none;
}
`;
export const OfferPackage = styled(Box)`
font-family: "Open Sans";
@@ -84,9 +118,6 @@ export const OfferViews = styled(Box)`
color: ${selectedTheme.primaryText};
line-height: 16px;
font-size: 12px;
@media (max-width: 1200px) {
display: none;
}
`;
export const OfferDescriptionTitle = styled(Box)`
font-family: "Open Sans";
@@ -166,4 +197,50 @@ export const MessageIcon = styled(IconButton)`
border-radius: 100%;
padding-top: 2px;
text-align: center;
@media (max-width: 600px) {
width: 30px;
height: 30px;
top: 16px;
right: 16px;
padding: 0;
& button svg {
width: 16px;
height: 16px;
position: relative;
top: -3px;
left: -2.4px;
}
}
`;
export const OfferImageContainer = styled(Box)`
min-width: 144px;
min-height: 144px;
width: 144px;
height: 144px;
@media (max-width: 600px) {
min-width: 108px;
min-height: 108px;
width: 108px;
height: 108px;
border-radius: 4px;
overflow: hidden;
box-shadow: 4px 4px 9px rgba(0, 0, 0, 0.12);
}
`;
export const OfferTitleAboveImage = styled(OfferTitle)`
padding-bottom: 12px;
padding-top: 5px;
padding-left: 1px;
display: block;
@media (min-width: 551px) {
display: none;
}
`;
export const EyeIcon = styled(Eye)`
width: 12px;
height: 11px;
@media (max-width: 600px) {
position: relative;
top: 1px !important;
}
`;

+ 365
- 188
src/components/Header/Header.js Переглянути файл

@@ -1,16 +1,26 @@
import React, { useState, useMemo, useEffect, useRef } from "react";
import {
AddOfferButton,
AuthButtonsContainer,
AuthButtonsDrawerContainer,
DrawerContainer,
EndIcon,
FilterContainer,
FilterIcon,
HeaderContainer,
LoginButton,
LogoContainer,
RegisterButton,
SearchIcon,
SearchInput,
SearchInputMobile,
ToggleDrawerButton,
ToolsButtonsContainer,
ToolsContainer,
UserButton,
UserName,
} from "./Header.styled";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
@@ -33,26 +43,67 @@ import { ReactComponent as LogoHorizontal } from "../../assets/images/svg/logo-h
import selectedTheme from "../../themes";
import { useTranslation } from "react-i18next";
import { IconButton } from "../Buttons/IconButton/IconButton";
import { Icon } from "../Icon/Icon";
import { useSelector } from "react-redux";
import { selectJWTToken } from "../../store/selectors/loginSelectors";
import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useSearch } from "../../hooks/useSearch";
import { selectProfileName } from "../../store/selectors/profileSelectors";
import { fetchProfile } from "../../store/actions/profile/profileActions";
import { useHistory, useRouteMatch } from "react-router-dom";
import { LOGIN_PAGE, REGISTER_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";

const Header = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const [openFilters, setOpenFilters] = useState(false);
const [numberOfFilters, setNumberOfFilters] = useState(0);
const { t } = useTranslation();
const theme = useTheme();
const searchRef = useRef(null);
const matches = useMediaQuery(theme.breakpoints.down("md"));
const user = useSelector(selectJWTToken);
const user = useSelector(selectUserId);
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();
useEffect(() => {
if (user?.length > 1) {
dispatch(fetchProfile(user));
}
}, [user]);
useEffect(() => {
setUserPopoverOpen(false);
setUserAnchorEl(null);
return () => {
setUserPopoverOpen(false);
setUserAnchorEl(null);
};
}, []);
useEffect(() => {
setNumberOfFilters(filters.calculateFiltersChosen());
}, [
filters.selectedCategory,
filters.selectedLocations,
filters.selectedSubcategory,
]);

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};

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

const [postsPopoverOpen, setPostsPopoverOpen] = useState(false);
const [postsAnchorEl, setPostsAnchorEl] = useState(null);
@@ -71,211 +122,337 @@ const Header = () => {
location.pathname === "/login" ||
location.pathname === "/register" ||
location.pathname === "/forgot-password" ||
location.pathname === "/reset-password"
location.pathname === "/reset-password" ||
location.pathname === "/"
) {
shouldShowHeader = false;
}
if (location.pathname === "/" && user.JwtToken?.length === 0) {
if (location.pathname === "/" && user?.length === 0) {
shouldShowHeader = false;
}
setShouldShow(shouldShowHeader);
}, [location, user]);
}, [location.pathname, user, routeMatch]);

let listener;
const handleFocusSearch = () => {
listener = (event) => {
if (event.keyCode === 13) {
event.preventDefault();
search.searchOffers(searchRef.current.value)
}
}
searchRef.current.addEventListener('keyup', listener);
const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};
const handleBlurSearch = () => {
searchRef.current.removeEventListener('keyup', listener);
const handleNavigateLogin = () => {
setShouldShow(false);
history.push(LOGIN_PAGE);
};
const handleNavigateRegister = () => {
setShouldShow(false);
history.push(REGISTER_PAGE);
};

const drawerContent = useMemo(
() => (
<DrawerContainer>
<PrimaryButton
type="submit"
variant="contained"
height="36px"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor="black"
onClick={() => handleToggleDrawer()}
>
{t("header.addOffer")}
</PrimaryButton>
<ToolsContainer mobile>
<IconButton
onClick={(e) => {
setPostsPopoverOpen(true);
setPostsAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<Autorenew />
<Typography sx={{ ml: 2 }}>Moje objave</Typography>
</IconButton>
<IconButton
onClick={(e) => {
setMsgPopoverOpen(true);
setMsgAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<Badge badgeContent={3} color="primary">
<MailIcon color="action" />
</Badge>
<Typography sx={{ ml: 2 }}>Moje poruke</Typography>
</IconButton>
<IconButton
onClick={(e) => {
setUserPopoverOpen(true);
setUserAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<AccountCircle />
<Typography sx={{ ml: 2 }}>Moj profil</Typography>
</IconButton>
</ToolsContainer>
{user ? (
<React.Fragment>
<PrimaryButton
type="submit"
variant="contained"
height="36px"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor="black"
onClick={() => handleToggleDrawer()}
>
{t("header.addOffer")}
</PrimaryButton>
<ToolsContainer mobile>
<IconButton
onClick={(e) => {
setPostsPopoverOpen(true);
setPostsAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<Autorenew />
<Typography sx={{ ml: 2 }}>Moje objave</Typography>
</IconButton>
<IconButton
onClick={(e) => {
setMsgPopoverOpen(true);
setMsgAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<Badge badgeContent={3} color="primary">
<MailIcon color="action" />
</Badge>
<Typography sx={{ ml: 2 }}>Moje poruke</Typography>
</IconButton>
<IconButton
onClick={(e) => {
setUserPopoverOpen(true);
setUserAnchorEl(e.currentTarget);
}}
sx={{ borderRadius: "4px" }}
>
<AccountCircle />
<Typography sx={{ ml: 2 }}>Moj profil</Typography>
</IconButton>
</ToolsContainer>
</React.Fragment>
) : (
<AuthButtonsDrawerContainer>
<RegisterButton
type="submit"
variant="contained"
height="36px"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor={selectedTheme.primaryDarkText}
onClick={handleNavigateRegister}
>
{t("register.headerTitle")}
</RegisterButton>
<LoginButton
type="submit"
variant="contained"
height="36px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryIconBackgroundColor}
onClick={handleNavigateLogin}
>
{t("login.headerTitle")}
</LoginButton>
</AuthButtonsDrawerContainer>
)}
</DrawerContainer>
),
[handleToggleDrawer]
);

let listener;
const handleFocusSearch = () => {
listener = (event) => {
if (event.keyCode === 13) {
event.preventDefault();
handleSearch(searchRef.current.value);
}
};
searchRef.current.addEventListener("keyup", listener);
};
const handleBlurSearch = () => {
searchRef.current.removeEventListener("keyup", listener);
};
const handleSearch = (value) => {
console.log(value);
if (value.length === 0) return;
search.searchOffers(value);
};
const toggleFilters = () => {
setOpenFilters((prevState) => !prevState);
};

return (
<AppBar
elevation={0}
position="fixed"
// positionFixed
sx={{ backgroundColor: "white" }}
style={{ display: shouldShow ? "flex" : "none" }}
>
<Toolbar>
<ToolsContainer>
<LogoContainer>
<LogoHorizontal />
</LogoContainer>
{matches && (
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={drawerContent}
<HeaderContainer style={{ display: shouldShow ? "block" : "none" }}>
<AppBar
elevation={0}
position="fixed"
// positionFixed
sx={{ backgroundColor: "white" }}
>
<Toolbar>
<ToolsContainer>
<LogoContainer>
<LogoHorizontal />
</LogoContainer>
{matches && (
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={drawerContent}
/>
)}
<SearchInput
fullWidth
InputProps={{
endAdornment: (
<EndIcon size="36px">
<SearchIcon
onClick={() => handleSearch(searchRef.current.value)}
/>
</EndIcon>
),
}}
placeholder={t("header.searchOffers")}
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
ref={searchRef}
/>
)}
<SearchInput
fullWidth
InputProps={{
startAdornment: (
<Icon size="36px">
<SearchIcon />
</Icon>
),
}}
label={t("header.searchOffers")}
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
ref={searchRef}
/>
<ToolsButtonsContainer mobile={matches}>
{matches ? (
<ToggleDrawerButton>
<IconButton onClick={handleToggleDrawer}>
<MenuOutlinedIcon />
</IconButton>
</ToggleDrawerButton>
{user ? (
<ToolsButtonsContainer mobile={matches}>
{matches ? (
<ToggleDrawerButton>
<IconButton onClick={handleToggleDrawer}>
<MenuOutlinedIcon />
</IconButton>
</ToggleDrawerButton>
) : (
<React.Fragment>
<AddOfferButton
type="submit"
variant="contained"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor={selectedTheme.primaryDarkText}
onClick={() => {
setUserPopoverOpen(false);
setUserAnchorEl(null);
}}
>
{t("header.addOffer")}
</AddOfferButton>
<IconButton
onClick={(e) => {
setPostsPopoverOpen(true);
setPostsAnchorEl(e.currentTarget);
}}
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<Autorenew />
</IconButton>
<IconButton
onClick={(e) => {
setMsgPopoverOpen(true);
setMsgAnchorEl(e.currentTarget);
}}
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<Badge badgeContent={3} color="primary">
<MailIcon />
</Badge>
</IconButton>
<UserButton
onClick={(e) => {
setUserPopoverOpen(true);
setUserAnchorEl(e.currentTarget);
}}
>
<UserName>{name}</UserName>
<IconButton
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<AccountCircle />
</IconButton>
</UserButton>
</React.Fragment>
)}
</ToolsButtonsContainer>
) : (
<React.Fragment>
<AddOfferButton
type="submit"
variant="contained"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor={selectedTheme.primaryDarkText}
>
{t("header.addOffer")}
</AddOfferButton>
<IconButton
onClick={(e) => {
setPostsPopoverOpen(true);
setPostsAnchorEl(e.currentTarget);
}}
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<Autorenew />
</IconButton>
<IconButton
onClick={(e) => {
setMsgPopoverOpen(true);
setMsgAnchorEl(e.currentTarget);
}}
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<Badge badgeContent={3} color="primary">
<MailIcon />
</Badge>
</IconButton>
<UserButton
onClick={(e) => {
setUserPopoverOpen(true);
setUserAnchorEl(e.currentTarget);
}}
>
<UserName>{name}</UserName>
<IconButton
style={{
background: selectedTheme.primaryIconBackgroundColor,
color: selectedTheme.primaryPurple,
}}
>
<AccountCircle />
</IconButton>
</UserButton>
</React.Fragment>
<AuthButtonsContainer mobile={matches}>
{matches ? (
<ToggleDrawerButton>
<IconButton onClick={handleToggleDrawer}>
<MenuOutlinedIcon />
</IconButton>
</ToggleDrawerButton>
) : (
<React.Fragment>
<LoginButton
type="submit"
variant="contained"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.offerBackgroundColor}
onClick={handleNavigateLogin}
>
{t("login.headerTitle")}
</LoginButton>
<RegisterButton
type="submit"
variant="contained"
fullWidth
buttoncolor={selectedTheme.primaryYellow}
textcolor={selectedTheme.primaryDarkText}
onClick={handleNavigateRegister}
>
{t("register.headerTitle")}
</RegisterButton>
</React.Fragment>
)}
</AuthButtonsContainer>
)}
</ToolsButtonsContainer>
</ToolsContainer>
</Toolbar>
<PopoverComponent
anchorEl={postsAnchorEl}
open={postsPopoverOpen}
onClose={() => {
setPostsPopoverOpen(false);
setPostsAnchorEl(null);
}}
content={<MyPosts />}
/>
<PopoverComponent
anchorEl={msgAnchorEl}
open={msgPopoverOpen}
onClose={() => {
setMsgPopoverOpen(false);
setMsgAnchorEl(null);
</ToolsContainer>
</Toolbar>
{user && (
<React.Fragment>
<PopoverComponent
anchorEl={postsAnchorEl}
open={postsPopoverOpen}
onClose={() => {
setPostsPopoverOpen(false);
setPostsAnchorEl(null);
}}
content={<MyPosts />}
/>
<PopoverComponent
anchorEl={msgAnchorEl}
open={msgPopoverOpen}
onClose={() => {
setMsgPopoverOpen(false);
setMsgAnchorEl(null);
}}
content={<MyMessages />}
/>
<PopoverComponent
anchorEl={userAnchorEl}
open={userPopoverOpen}
onClose={() => {
setUserPopoverOpen(false);
setUserAnchorEl(null);
}}
content={<MyProfile />}
/>
</React.Fragment>
)}
</AppBar>
<SearchInputMobile
fullWidth
ref={searchMobileRef}
InputProps={{
endAdornment: (
<React.Fragment>
<FilterContainer number={numberOfFilters}>
<FilterIcon onClick={toggleFilters} />
</FilterContainer>
<EndIcon size="36px">
<SearchIcon
onClick={() => handleSearch(searchMobileRef.current.value)}
/>
</EndIcon>
</React.Fragment>
),
}}
content={<MyMessages />}
placeholder={t("header.searchOffers")}
italicPlaceholder
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
/>
<PopoverComponent
anchorEl={userAnchorEl}
open={userPopoverOpen}
onClose={() => {
setUserPopoverOpen(false);
setUserAnchorEl(null);
}}
content={<MyProfile />}
<FilterCard
responsive={true}
responsiveOpen={openFilters}
closeResponsive={toggleFilters}
/>
</AppBar>
</HeaderContainer>
);
};

Header.propTypes = {
isGrid: PropTypes.bool,
};

export default Header;

+ 127
- 14
src/components/Header/Header.styled.js Переглянути файл

@@ -3,26 +3,38 @@ 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)`
margin-left: 3.8rem;
background-color: #f4f4f4;
width: 45%;
flex: 3;
max-width: 520px;
margin-right: 30px;
font-family: "Open Sans";
@media (max-width: 1700px) {
margin-left: 15%;
}
@media (max-width: 1550px) {
margin-left: 15%;
}
@media (max-width: 1320px) {
margin-left: 7%;
}
@media (max-width: 1100px) {
width: 36%;
}
@media (max-width: 1000px) {
width: 54%;
margin-left: 2.8rem;
width: 36%;
margin-left: 5%;
margin-right: 10px;
}
@media (max-width: 600px) {
width: 60%;
margin-left: 2rem;
@media (max-width: 550px) {
display: none;
width: 0;
}
`;
export const DrawerContainer = styled(Box)`
@@ -38,12 +50,11 @@ export const DrawerContainer = styled(Box)`
export const ToolsContainer = styled(Box)`
display: flex;
flex-direction: row;

justify-content: ${(props) => (props.mobile ? "center" : "space-between")};
align-items: ${(props) => (props.mobile ? "start" : "center")};
${(props) => !props.mobile && `width: 100%;`}
& div button {
${props => props.mobile && `width: auto;`}
${(props) => props.mobile && `width: auto;`}
}
`;
export const LogoContainer = styled(Box)`
@@ -55,19 +66,21 @@ export const ToolsButtonsContainer = styled(Box)`
display: flex;
flex: 4;
justify-content: space-between;
min-width: ${props => props.mobile ? "40px" : "600px"};
min-width: ${(props) => (props.mobile ? "40px" : "600px")};
max-width: 600px;
align-items: center;
flex-wrap: nowrap;
@media (max-width: 1400px) {
min-width: 450px;
}
@media (max-width: 1200px) {
min-width: 400px;
}
@media (max-width: 950px) {
min-width: 250px;
}
@media (max-width: 800px) {
@media (max-width: 900px) {
flex: 0.35;
min-width: 0px;
width: 0px;
width: 60px;
justify-content: right;
}
`;
export const ToggleDrawerButton = styled(Box)`
@@ -78,15 +91,22 @@ export const AddOfferButton = styled(PrimaryButton)`
width: 180px;
font-weight: 600;
`;
export const EndIcon = styled(Icon)``;
export const SearchIcon = styled(Search)`
position: relative;
top: 11px;
left: 4px;
cursor: pointer;
color: ${selectedTheme.primaryPurple};
& path {
width: 18px;
height: 18px;
}
@media (max-width: 600px) {
height: 14px;
width: 14px;
left: 11px;
}
`;
export const UserButton = styled(Box)`
display: flex;
@@ -101,3 +121,96 @@ export const UserName = styled(Typography)`
font-weight: 600;
white-space: nowrap;
`;
export const RegisterButton = styled(PrimaryButton)`
height: 49px;
width: 180px;
font-weight: 600;
@media (max-width: 550px) {
margin-bottom: 20px;
}
`;
export const LoginButton = styled(PrimaryButton)`
height: 49px;
width: 180px;
font-weight: 600;
margin-right: 10px;
`;
export const AuthButtonsContainer = styled(Box)`
display: flex;
justify-content: flex-start;
flex: 1;
min-width: ${(props) => (props.mobile ? "40px" : "200px")};
max-width: 600px;
align-items: flex-start;
flex-wrap: nowrap;
margin-left: 40px;
& div {
margin-left: 20px;
}
@media (max-width: 1300px) {
margin-left: 0;
}
@media (max-width: 1200px) {
min-width: 400px;
}
@media (max-width: 900px) {
min-width: 0px;
width: 0px;
justify-content: right;
}
`;

export const AuthButtonsDrawerContainer = styled(Box)`
position: relative;
left: 10px;
height: 200px;
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-around;
`;
export const SearchInputMobile = styled(SearchInput)`
@media (max-width: 550px) {
display: block;
position: relative;
width: 80%;
top: 70px;
height: 46px;
left: -50px;
font-family: "Open Sans";
& div {
background-color: white;
height: 40px;
overflow: visible;
& input {
font-size: 14px !important;
}
}
}
@media (min-width: 551px) {
display: none;
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)``;

+ 2
- 1
src/components/Icon/IconWithNumber/IconWithNumber.js Переглянути файл

@@ -4,7 +4,7 @@ import { IconWithNumberContainer, Number } from './IconWithNumber.styled'

const IconWithNumber = (props) => {
return (
<IconWithNumberContainer>
<IconWithNumberContainer className={props.className}>
{props.children}
{props.number > 0 && <Number>{props.number}</Number>}
</IconWithNumberContainer>
@@ -14,6 +14,7 @@ const IconWithNumber = (props) => {
IconWithNumber.propTypes = {
children: PropTypes.node,
number: PropTypes.number,
className: PropTypes.string,
}

export default IconWithNumber

+ 20
- 10
src/components/MarketPlace/Header/Header.js Переглянути файл

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import {
HeaderAltLocation,
HeaderButton,
HeaderButtons,
HeaderContainer,
@@ -8,15 +9,17 @@ import {
HeaderOptions,
HeaderSelect,
IconStyled,
SelectOption,
} from "./Header.styled";
import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg";
import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-grid-line.svg";
import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg";
import selectedTheme from "../../../themes";
import Option from "../../Select/Option/Option";
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";

const DownArrow = (props) => (
<IconStyled {...props}>
@@ -27,6 +30,7 @@ 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("SVE KATEGORIJE");

@@ -36,7 +40,7 @@ const Header = (props) => {

useEffect(() => {
if (filters.isApplied) {
let headerStringLocal = "SVE KATEGORIJE";
let headerStringLocal = "Sve kategorije";
if (filters.selectedCategory?.name) {
headerStringLocal = filters.selectedCategory.name;
if (filters.selectedSubcategory?.name) {
@@ -74,7 +78,18 @@ const Header = (props) => {

return (
<HeaderContainer>
<HeaderLocation>{headerString}</HeaderLocation>
<Tooltip title={headerString}>
{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>
)}
</Tooltip>
<HeaderOptions>
<HeaderButtons>
<HeaderButton
@@ -101,21 +116,16 @@ const Header = (props) => {
<HeaderSelect
value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value}
IconComponent={DownArrow}
width="209px"
height="34px"
onChange={handleChangeSelect}
>
{Object.keys(sortEnum).map((property) => {
return (
<Option
<SelectOption
value={sortEnum[property].value}
key={sortEnum[property].value}
style={{
display: sortEnum[property].value === 0 ? "none" : "flex",
}}
>
{sortEnum[property].mainText}
</Option>
</SelectOption>
);
})}
</HeaderSelect>

+ 82
- 45
src/components/MarketPlace/Header/Header.styled.js Переглянути файл

@@ -1,58 +1,95 @@
import { Box, MenuItem } from "@mui/material";
import { Box, MenuItem, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import Option from "../../Select/Option/Option";
import Select from "../../Select/Select";

export const HeaderContainer = styled(Box)`
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
`
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
`;
export const HeaderLocation = styled(Box)`
font-family: "Open Sans";
color: ${selectedTheme.primaryPurple};
font-weight: 700;
line-height: 22px;
font-size: 16px;
flex: 2;
`
export const HeaderButton = styled(IconButton)`
padding: 2px 10px;
@media (max-width: 1500px) {
display: none;
font-family: "Open Sans";
color: ${selectedTheme.primaryPurple};
font-weight: 700;
line-height: 22px;
font-size: 16px;
flex: 2;
max-width: ${props => props.initial ? "fit-content" : "50%"};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:after {
content: ${props => props.initial ? `":"` : `""`};
@media (max-width: 600px) {
content: "";
}
`
}
@media (max-width: 600px) {
font-size: 14px;
padding-top: 3px;
}
`;
export const HeaderButton = styled(IconButton)`
padding: 2px 10px;
@media (max-width: 1500px) {
display: none;
}
`;
export const HeaderOptions = styled(Box)`
display: flex;
flex-direction: row;
flex: 1;
justify-content: end;
`
display: flex;
flex-direction: row;
flex: 1;
justify-content: end;
`;
export const HeaderSelect = styled(Select)`
width: 210px;
height: 35px;
font-family: "Open Sans";
margin-top: 3px;
font-weight: 400;
position: relative;
left: -5px;
& div:first-child {
padding-left: 8px;
}
`
width: 210px;
height: 35px;
font-family: "Open Sans";
margin-top: 3px;
font-weight: 400;
position: relative;
left: -5px;
& div:first-child {
padding-left: 8px;
}

@media (max-width: 650px) {
width: 144px;
height: 30px;
font-size: 14px;
}
`;
export const SelectItem = styled(MenuItem)`
font-family: "Open Sans";
`
font-family: "Open Sans";
`;
export const SelectOption = styled(Option)`
@media (max-width: 600px) {
height: 20px !important;
min-height: 35px;
margin: 2px;
}
`;
export const IconStyled = styled(Box)`
position: relative;
top: 0;
right: 10px;
`
position: relative;
top: 0;
right: 10px;
`;
export const HeaderButtons = styled(Box)`
flex-direction: row;
display: flex;
justify-content: space-between;
margin-right: 40px;
`
flex-direction: row;
display: flex;
justify-content: space-between;
margin-right: 40px;
`;
export const HeaderAltLocation = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryText};
margin-left: 5px;
@media (max-width: 600px) {
display: none;
}
`

+ 2
- 2
src/components/MarketPlace/MarketPlace.styled.js Переглянути файл

@@ -4,7 +4,7 @@ import styled from "styled-components";
export const MarketPlaceContainer = styled(Box)`
height: 100%;
margin: 0 70px;
@media (max-width: 600px) {
margin: 0 1.8rem;
@media (max-width: 550px) {
margin: -30px 1.8rem;
}
`;

+ 83
- 46
src/components/MarketPlace/Offers/Offers.js Переглянути файл

@@ -2,72 +2,103 @@ import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import {
fetchMoreOffers,
fetchOffers,
} from "../../../store/actions/offers/offersActions";
import { fetchOffers } from "../../../store/actions/offers/offersActions";
import { useDispatch, useSelector } from "react-redux";
import {
selectNoMoreOffers,
selectOffers,
selectPinnedOffers,
selectTotalOffers,
} from "../../../store/selectors/offersSelectors";
import useFilters from "../../../hooks/useFilters";
// import useFilters from "../../../hooks/useFilters";
import Paging from "../../Paging/Paging";
// import { convertQueryString } from "../../../util/helpers/queryHelpers";
import { HOME_PAGE } from "../../../constants/pages";
import { useHistory } from "react-router-dom";
// import qs from "query-string";
// import useSorting from "../../../hooks/useSorting";
import { useQueryString } from "../../../hooks/useQueryString";

const Offers = (props) => {
const filters = useFilters();
const [page, setPage] = useState(2);
const [initialLoad, setInitialLoad] = useState(true);
// const filters = useFilters();
const [page, setPage] = useState(1);
// const [initialLoad, setInitialLoad] = useState(true);
const pinnedOffers = useSelector(selectPinnedOffers);
const offers = useSelector(selectOffers);
const total = useSelector(selectTotalOffers);
const history = useHistory();
// const sorting = useSorting();
const dispatch = useDispatch();
const offersRef = useRef(null);
const noMoreOffersStatus = useSelector(selectNoMoreOffers);
let timeout = null;
let listener;
const queryStringHook = useQueryString();

// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);
useEffect(() => {
listener = () => {
if (
!noMoreOffersStatus &&
offers?.length > 0 &&
window.scrollY + window.innerHeight >
window.document.body.offsetHeight * 0.8
) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
}, 5000);
dispatch(
fetchMoreOffers({ page: page, queryString: filters.queryString })
);
}
}
};
window.addEventListener("scroll", listener);
return () => window.removeEventListener("scroll", listener);
}, [page, noMoreOffersStatus, filters.queryString, timeout, offers]);

useEffect(() => {
setPage(Math.floor((offers.length + pinnedOffers.length ) / 10) + 1);
}, [offers, pinnedOffers]);
let queryObject = queryStringHook.getQueryObject();
if (queryObject.page && queryObject.page !== 1) {
setPage(parseInt(queryObject.page));
}
}, [history.location.search]);

useEffect(() => {
dispatch(fetchOffers({ page: 1 }));
}, []);
// console.log("sorting.loadedQS", sorting.loadedQS);
// console.log("queryString", queryString)
if (queryStringHook.loadedFromURL) {
dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString }));
history.push({
pathname: HOME_PAGE,
search: queryStringHook.getGlobalQueryString(),
});
window.scrollTo({
top: 0,
behavior: "smooth",
});
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString())
setPage(parseInt(queryObject.get("page")));
} else {
setPage(1);
}
} else {
queryStringHook.appendMultipleToQueryString([
{ key: "size", value: "10" },
{ key: "page", value: "1" },
]);
// queryStringHook.appendToQueryString("page", 1);
}
}, [queryStringHook.loadedFromURL, queryStringHook.queryString]);

useEffect(() => {
if (filters.queryString) {
if (filters.queryString.length > 1) {
if (initialLoad) {
dispatch(fetchOffers({ page: 1, queryString: filters.queryString }));
setInitialLoad(false);
// if (page !== 1) {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString()) {
queryStringHook.appendToQueryString("page", page);
}
} else {
setInitialLoad(false);
queryStringHook.appendToQueryString("page", page);
}
}
}, [filters.queryString, initialLoad]);
console.log("PAGE KONZOLAAAAAAAAAAAAAAAAAAAAAA")
// }
}, [page]);

// useEffect(() => {
// if (filters.queryString) {
// if (filters.queryString.length > 1) {
// if (initialLoad) {
// dispatch(fetchOffers({ page: 1, queryString: filters.queryString }));
// setInitialLoad(false);
// }
// } else {
// setInitialLoad(false);
// }
// }
// }, [filters.queryString, initialLoad]);

const handleDifferentPage = (pageNum) => {
setPage(pageNum);
};

return (
<OffersContainer ref={offersRef}>
@@ -85,6 +116,12 @@ const Offers = (props) => {
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />
);
})}
<Paging
totalElements={total}
elementsPerPage={10}
current={page}
changePage={handleDifferentPage}
/>
</OffersContainer>
);
};

+ 3
- 0
src/components/MarketPlace/Offers/Offers.styled.js Переглянути файл

@@ -6,4 +6,7 @@ export const OffersContainer = styled(Box)`
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 5px;
position: relative;
padding-bottom: 60px;
`;

+ 76
- 0
src/components/Paging/Paging.js Переглянути файл

@@ -0,0 +1,76 @@
import React from "react";
import PropTypes from "prop-types";
import {
Arrow,
ArrowIcon,
PageNumber,
PagingContainer,
ThreeDots,
} from "./Paging.styled";

const Paging = (props) => {
const pages = props.pages
? props.pages
: props.totalElements
? Math.ceil(props.totalElements / props.elementsPerPage)
: 1;
let moving = 0;
const pagesAsArray = Array.apply(null, Array(5)).map(() => {});
const threeDotsBefore = props.current - 2 > 1;
const threeDotsAfter = props.current + 2 < pages;
return (
<PagingContainer>
<Arrow
onClick={() => props.changePage(props.current - 1)}
disabled={props.current - 1 < 1}
>
<ArrowIcon side="left" />
</Arrow>
{threeDotsBefore && (
<React.Fragment>
<PageNumber onClick={() => props.changePage(1)}>1</PageNumber>
{props.current - 3 !== 1 && <ThreeDots>...</ThreeDots>}
</React.Fragment>
)}
{pagesAsArray.map((item, index) => {
const pageNum = props.current - 2 + moving++;
if (pageNum > pages ) return;
if (pageNum < 1) return;
return (
<PageNumber
current={pageNum === props.current}
key={index}
onClick={() => props.changePage(pageNum)}
>
{pageNum}
</PageNumber>
);
})}
{threeDotsAfter && (
<React.Fragment>
{props.current + 3 !== pages && <ThreeDots>...</ThreeDots>}
<PageNumber onClick={() => props.changePage(pages)}>
{pages}
</PageNumber>
</React.Fragment>
)}
<Arrow
onClick={() => props.changePage(props.current + 1)}
disabled={props.current + 1 > pages}
>
<ArrowIcon side="right" />
</Arrow>
</PagingContainer>
);
};

Paging.propTypes = {
children: PropTypes.any,
totalElements: PropTypes.number,
elementsPerPage: PropTypes.number,
pages: PropTypes.number,
current: PropTypes.number,
changePage: PropTypes.func,
};

export default Paging;

+ 137
- 0
src/components/Paging/Paging.styled.js Переглянути файл

@@ -0,0 +1,137 @@
import { Box, Button } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../themes";
import { ReactComponent as DownArrow } from "../../assets/images/svg/arrow-down.svg";

export const PagingContainer = styled(Box)`
width: calc(100% / 12 * 8.5);
text-align: center;
font-family: "Open Sans";
display: flex;
flex: 1;
justify-content: center;
flex-direction: row;
margin-top: 5px;
margin-bottom: 10px;
position: absolute;
bottom: 0;
padding-left: 0;
padding-right: 0;
margin: auto;
left: 0;
right: 0;
`;
export const ArrowIcon = styled(DownArrow)`
${(props) =>
props.side === "left" &&
`
transform: rotate(180deg);
`}
width: 18px;
height: 18px;
& path {
${(props) =>
props.disabled &&
`
stroke: ${selectedTheme.iconStrokeDisabledColor}
`}
}
`;
export const Arrow = styled(Button)`
border: 1px solid ${selectedTheme.primaryPurple};
border-radius: 100%;
min-width: 36px;
width: 36px;
height: 36px;
display: block;
box-sizing: border-box;
cursor: pointer;
padding-left: 8px;
padding-top: 8px;
margin: auto 10px;
transition: 0.2s all ease;
&:hover {
background-color: ${selectedTheme.primaryPurple};
& svg path {
stroke: white;
}
}
${(props) =>
props.disabled &&
`
border 1px solid ${selectedTheme.iconStrokeDisabledColor};
& svg path {
stroke: ${selectedTheme.iconStrokeDisabledColor};
transition: 0.2s all ease;
}
`}
@media (max-width: 600px) {
width: 30px;
min-width: 30px;
height: 30px;
padding-top: 5px;
padding-left: 5px;
}
`;
export const PageNumber = styled(Box)`
color: ${(props) => (!props.current ? selectedTheme.primaryPurple : "white")};
font-weight: 600;
font-size: 16px;
line-height: 18px;
font-family: "Open Sans";
height: 40px;
min-width: 40px;
max-width: 40px;
width: 40px !important;
margin: 5px;
padding-top: 10px;
background-color: ${(props) => props.current && selectedTheme.primaryPurple};
border-radius: 100%;
position: relative;
top: 1px;
&:hover {
cursor: pointer;
${(props) =>
!props.current &&
`
color: ${props.current ? selectedTheme.primaryPurple : "white"};
background-color: ${!props.current && selectedTheme.primaryPurple};
`}
}
@media (max-width: 600px) {
height: 30px;
min-width: 30px;
max-width: 30px;
width: 30px !important;
padding-top: 6px;
font-size: 14px;
margin: 1px;
}
`;
export const ThreeDots = styled(Box)`
color: ${(props) => (!props.current ? selectedTheme.primaryPurple : "white")};
font-weight: 600;
font-size: 16px;
line-height: 18px;
font-family: "Open Sans";
height: 40px;
min-width: 40px;
max-width: 40px;
width: 40px !important;
margin: 5px;
padding-top: 10px;
background-color: ${(props) => props.current && selectedTheme.primaryPurple};
border-radius: 100%;
position: relative;
top: 1px;
@media (max-width: 600px) {
height: 10px;
min-width: 10px;
max-width: 10px;
width: 10px !important;
padding-top: 6px;
font-size: 14px;
margin: 1px;
}
`

+ 43
- 21
src/components/Popovers/HeaderPopover/HeaderPopover.js Переглянути файл

@@ -1,51 +1,68 @@
import React from "react";
import PropTypes from "prop-types";
import {
EyeIcon,
HeaderPopoverContainer,
PopoverButton,
PopoverButtonsContainer,
PopoverList,
PopoverListItem,
PopoverListItemAvatar,
PopoverListItemAvatarContainer,
PopoverListItemProfileAvatar,
PopoverListItemTextContainer,
PopoverNoItemsText,
PopoverTitle,
} from "./HeaderPopover.styled";
import selectedTheme from "../../../themes";

const HeaderPopover = (props) => {
return (
<HeaderPopoverContainer>
<PopoverTitle p={2}>{props.title}</PopoverTitle>
<PopoverList>
{props.items.map((item, index) => (
{props.items?.length > 0 ? props.items.map((item, index) => (
<PopoverListItem key={index}>
<PopoverListItemAvatarContainer>
{props.isProfile ? (
<PopoverListItemProfileAvatar alt={item.alt} src={item.src} />
) : (
<PopoverListItemAvatar alt={item.alt} src={item.src} />
)}
{props.isProfile ? (
<PopoverListItemProfileAvatar alt={item.alt} src={item.src} />
) : (
<PopoverListItemAvatar alt={item.alt} src={item.src} />
)}
</PopoverListItemAvatarContainer>
<PopoverListItemTextContainer
primary={item.title}
secondary={item.text}
>
</PopoverListItemTextContainer>
primary={item.title}
secondary={item.text}
></PopoverListItemTextContainer>
</PopoverListItem>
))}
)) : (
<PopoverNoItemsText>No items at the moment...</PopoverNoItemsText>
)}
</PopoverList>
<PopoverButton
sx={{
<PopoverButtonsContainer>
<PopoverButton
sx={{
mr: 2,
mb: 2,
}}
variant="text"
endIcon={<EyeIcon color={selectedTheme.iconYellowColor} />}
>
{props.buttonText}
</PopoverButton>
}}
variant="text"
endIcon={props.buttonIcon}
onClick={props.buttonOnClick}
>
{props.buttonText}
</PopoverButton>
{props.secondButtonText && (
<PopoverButton
sx={{
mr: 2,
mb: 2,
}}
variant="text"
endIcon={props.secondButtonIcon}
onClick={props.secondButtonOnClick}
>
{props.secondButtonText}
</PopoverButton>
)}
</PopoverButtonsContainer>
</HeaderPopoverContainer>
);
};
@@ -55,6 +72,11 @@ HeaderPopover.propTypes = {
items: PropTypes.array,
buttonText: PropTypes.string,
isProfile: PropTypes.bool,
secondButtonText: PropTypes.string,
buttonIcon: PropTypes.any,
secondButtonIcon: PropTypes.any,
buttonOnClick: PropTypes.func,
secondButtonOnClick: PropTypes.func,
};

export default HeaderPopover;

+ 15
- 1
src/components/Popovers/HeaderPopover/HeaderPopover.styled.js Переглянути файл

@@ -32,9 +32,10 @@ export const PopoverListItemAvatarContainer = styled(ListItemAvatar)`
`
export const PopoverButton = styled(Button)`
text-decoration: underline;
float: right;
color: ${selectedTheme.primaryPurple};
font-weight: 500;
text-align: right;
height: 20px;
`
export const PopoverListItemTextContainer = styled(ListItemText)`
& span {
@@ -53,4 +54,17 @@ export const EyeIcon = styled(Eye)`
& path {
stroke: ${selectedTheme.primaryYellow};
}
`
export const PopoverButtonsContainer = styled(Box)`
flex-direction: column;
display: flex;
align-items: flex-end;
`
export const PopoverNoItemsText = styled(Typography)`
text-align: center;
width: 100%;
font-weight: 600;
padding-top: 5px;
font-size: 13px;
font-family: "Open Sans";
`

+ 20
- 37
src/components/Popovers/MyMessages/MyMessages.js Переглянути файл

@@ -1,43 +1,18 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { fetchChats } from "../../../store/actions/chat/chatActions";
import { fetchHeaderChats } from "../../../store/actions/chat/chatActions";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import HeaderPopover from "../HeaderPopover/HeaderPopover";

const dummyData1 = [
{
alt: "Remy Sharp",
src: "/static/images/avatar/1.jpg",
title: "Coca-Cola",
text: "Kompresor je stigao. Samo...",
},
{
alt: "Travis Howard",
src: "/static/images/avatar/2.jpg",
title: "Voda Vrnjci",
text: "Poslao sam vodu. Ukupno i...",
},
];

const convertMessages = (messages) => {
const allMessages = [
...messages.chatsFromMyOffers,
...messages.initiatedChats,
];
const lastMessageDate = Math.max(
...allMessages.map((item) => new Date(item._modified))
);
const lastMessage = allMessages.find(
(item) =>
JSON.stringify(new Date(item._modified)) ===
JSON.stringify(new Date(lastMessageDate))
);
const lastSecondMessageDate = Math.max([...allMessages.filter(item => item._id !== lastMessage._id).map(item => new Date(item._modified))])
console.log(lastSecondMessageDate);
console.log(allMessages.filter(item => item._id !== lastMessage._id).map(item => new Date(item._modified)))
console.log(lastMessage);
return messages.map((item) => ({
alt: "Tekst",
src: item.interlocutorData.image,
title: item.interlocutorData.name,
text: item?.chat?.messages[0]?.text,
}));
};

export const MyMessages = () => {
@@ -45,14 +20,22 @@ export const MyMessages = () => {
const dispatch = useDispatch();
const userId = useSelector(selectUserId);
const chats = useSelector(selectLatestChats);
convertMessages(chats);
const [lastChats, setLastChats] = useState([]);

useEffect(() => {
if (userId?.length > 1) {
dispatch(fetchHeaderChats(userId));
}
}, [userId]);
useEffect(() => {
dispatch(fetchChats(userId));
}, []);
if (chats?.length > 0) {
setLastChats([...convertMessages(chats)]);
}
}, [chats]);
return (
<HeaderPopover
title={t("header.myMessages")}
items={dummyData1}
items={lastChats}
buttonText={t("header.checkEverything")}
/>
);

+ 64
- 19
src/components/Popovers/MyPosts/MyPosts.js Переглянути файл

@@ -1,29 +1,74 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { PostsImgSuit } from "./MyPosts.styled";

const dummyData2 = [
{
alt: "Remy Sharp",
src: "/static/images/avatar/1.jpg",
title: "Gitara",
text: (<React.Fragment><PostsImgSuit/> Player.rs</React.Fragment>)
},
{
alt: "Remy Sharp",
src: "/static/images/avatar/1.jpg",
title: "Gitara",
text: (<React.Fragment><PostsImgSuit/> Player.rs</React.Fragment>)
}
]
// const dummyData2 = [
// {
// alt: "Remy Sharp",
// src: "/static/images/avatar/1.jpg",
// title: "Gitara",
// text: (<React.Fragment><PostsImgSuit/> Player.rs</React.Fragment>)
// },
// {
// alt: "Remy Sharp",
// src: "/static/images/avatar/1.jpg",
// title: "Gitara",
// text: (<React.Fragment><PostsImgSuit/> Player.rs</React.Fragment>)
// }
// ]
import HeaderPopover from "../HeaderPopover/HeaderPopover";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { selectMineOffers } from "../../../store/selectors/offersSelectors";
import { fetchMineOffers } from "../../../store/actions/offers/offersActions";
import { selectProfileName } from "../../../store/selectors/profileSelectors";

export const MyPosts = () => {
const {t} = useTranslation();
const { t } = useTranslation();
const dispatch = useDispatch();
const mineOffers = useSelector(selectMineOffers);
const name = useSelector(selectProfileName);
const [arrayOfMineOffers, setArrayOfMineOffers] = useState([]);
useEffect(() => {
dispatch(fetchMineOffers());
}, []);
useEffect(() => {
if (mineOffers?.length > 0) {
if (mineOffers.length > 1) {
setArrayOfMineOffers(
[mineOffers[0], mineOffers[1]].map((item) => ({
alt: "Photo",
src: item.images[0],
title: item.name,
text: (
<React.Fragment>
<PostsImgSuit /> {name}
</React.Fragment>
),
}))
);
} else if (mineOffers.length > 0) {
setArrayOfMineOffers(
[mineOffers[0]].map((item) => ({
alt: "Photo",
src: item.images[0],
title: item.name,
text: (
<React.Fragment>
<PostsImgSuit /> {name}
</React.Fragment>
),
}))
);
} else {
setArrayOfMineOffers([])
}
}
});
return (
<HeaderPopover
title={t("header.myOffers")}
items={dummyData2}
buttonText={t("header.checkEverything")}/>
title={t("header.myOffers")}
items={arrayOfMineOffers}
buttonText={t("header.checkEverything")}
/>
);
};

+ 50
- 13
src/components/Popovers/MyProfile/MyProfile.js Переглянути файл

@@ -1,25 +1,62 @@
import React from "react";
import { ProfileImgPIB } from "./MyProfile.styled";
import React, { useEffect, useState } from "react";
import { LogoutIcon, ProfileImgPIB } from "./MyProfile.styled";
import HeaderPopover from "../HeaderPopover/HeaderPopover";
import { useTranslation } from "react-i18next";
const dummyData3 = [
{
alt: "Profile",
src: "/static/images/avatar/2.jpg",
title: "Player.rs",
text: <React.Fragment><ProfileImgPIB/> PIB - 1234567890 </React.Fragment>
},
];
import { useDispatch, useSelector } from "react-redux";
import { selectProfile } from "../../../store/selectors/profileSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import { fetchProfile } from "../../../store/actions/profile/profileActions";
import selectedTheme from "../../../themes";
import { EyeIcon } from "../HeaderPopover/HeaderPopover.styled";
import { logoutUser } from "../../../store/actions/login/loginActions";
import { useHistory } from "react-router-dom";
import { LOGIN_PAGE } from "../../../constants/pages";

export const MyProfile = () => {
const {t} = useTranslation();
const { t } = useTranslation();
const profile = useSelector(selectProfile);
const userId = useSelector(selectUserId);
const dispatch = useDispatch();
const history = useHistory();
const [profileAsArray, setProfileAsArray] = useState([]);
useEffect(() => {
if (userId?.length > 1) {
dispatch(fetchProfile(userId));
}
}, [userId]);
useEffect(() => {
if (profile?.statistics) {
setProfileAsArray([
{
alt: "Profile",
src: `${profile.image}`,
title: profile.company.name,
text: (
<React.Fragment>
<ProfileImgPIB />
PIB - {profile.company.PIB}
</React.Fragment>
),
},
]);
}
}, [profile]);
const handleLogoutSuccess = () => {
history.replace(LOGIN_PAGE);
};
const handleLogout = () => {
dispatch(logoutUser(handleLogoutSuccess));
};
return (
<HeaderPopover
title={t("header.myProfile")}
items={dummyData3}
items={profileAsArray}
buttonText={t("header.checkProfile")}
buttonIcon={<EyeIcon color={selectedTheme.iconYellowColor} />}
isProfile
secondButtonIcon={<LogoutIcon color={selectedTheme.iconYellowColor} />}
secondButtonText={"Odjavite se"}
secondButtonOnClick={handleLogout}
/>
);
};

+ 3
- 0
src/components/Popovers/MyProfile/MyProfile.styled.js Переглянути файл

@@ -1,6 +1,7 @@
import { TextField, Avatar } from "@mui/material";
import styled from "styled-components";
import PIB from '@mui/icons-material/AdminPanelSettingsOutlined';
import {ReactComponent as Logout} from "../../../assets/images/svg/log-out.svg";

export const ProfileImgPIB = styled(PIB)`
width: 1rem;
@@ -28,4 +29,6 @@ export const SearchInput = styled(TextField)`
@media (max-width: 600px) {
width: 36%;
}
`
export const LogoutIcon = styled(Logout)`
`

+ 4
- 0
src/components/Select/Select.styled.js Переглянути файл

@@ -1,5 +1,6 @@
import { Box, Select } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../themes";

export const SelectStyled = styled(Select)`
width: ${props => props.width};
@@ -9,6 +10,9 @@ export const SelectStyled = styled(Select)`
font-weight: 600;
font-family: "Open Sans";
cursor: pointer;
& fieldset {
border-color: ${props => props.borderColor ? props.borderColor : selectedTheme.primaryPurple} !important;
}
`
export const SelectIcon = styled(Box)`
position: relative;

+ 0
- 1
src/components/TextFields/TextField/TextField.js Переглянути файл

@@ -13,7 +13,6 @@ export const TextField = React.forwardRef((props, ref) => {
setIsFieldEmpty(false);
}
}, [props.value]);

return (
<TextFieldContainer
style={props.containerStyle}

+ 150
- 95
src/hooks/useFilters.js Переглянути файл

@@ -1,14 +1,14 @@
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
// import { useHistory } from "react-router-dom";
import { fetchCategories } from "../store/actions/categories/categoriesActions";
import {
setFilteredCategory,
setFilteredLocations,
setFilteredSubcategory,
setIsAppliedStatus,
setQueryString,
// setQueryString,
} from "../store/actions/filters/filtersActions";
import { fetchLocations } from "../store/actions/locations/locationsActions";
import {
@@ -17,32 +17,36 @@ import {
} from "../store/selectors/categoriesSelectors";
import {
selectAppliedStatus,
selectQueryString,
// selectQueryString,
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSubcategory,
} from "../store/selectors/filtersSelectors";
import qs from "query-string";
import { selectLocations } from "../store/selectors/locationsSelectors";
import { fetchOffers } from "../store/actions/offers/offersActions";
import { HOME_PAGE } from "../constants/pages";
import { convertQueryString } from "../util/helpers/queryHelpers";
// import { fetchOffers } from "../store/actions/offers/offersActions";
// import { HOME_PAGE } from "../constants/pages";
// import { convertQueryString } from "../util/helpers/queryHelpers";
// import qs from "query-string";
import { useQueryString } from "./useQueryString";
// import qs from "query-string";



const useFilters = () => {
const queryString = useSelector(selectQueryString);
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
const [loadedFromQS, setLoadedFromQS] = useState(false);
const [loaded, setLoadedStatus] = useState(false);
const isApplied = useSelector(selectAppliedStatus);
const history = useHistory();
// const history = useHistory();
const categories = useSelector(selectCategories);
const subcategories = useSelector(
selectSubcategories(selectedCategory?.name)
);
const locations = useSelector(selectLocations);
const dispatch = useDispatch();
const queryStringHook = useQueryString();
useEffect(() => {
if (!loaded) {
dispatch(fetchCategories());
@@ -50,80 +54,107 @@ const useFilters = () => {
setLoadedStatus(true);
}
}, [categories, locations]);

// useEffect(() => {
// if (loadedFromQS) {
// makeQueryString();
// }
// }, [selectedCategory, selectedLocations, selectedSubcategory, loadedFromQS]);

useEffect(() => {
if (!loadedFromQS) {
if (categories && locations) {
const queryString = history.location.search.substring(1);
const queryObject = qs.parse(queryString);
let category;
if (queryObject.category) {
category = categories.find(
(item) => item.name === queryObject.category.toString()
);
setSelectedCategory(category);
}
if (queryObject.subcategory) {
setSelectedSubcategory(
category.subcategories.find(
(item) =>
item.name.toString() === queryObject.subcategory.toString()
)
);
}
if (queryObject.location) {
let locationsToPush = [];
if (Array.isArray(queryObject.location)) {
queryObject.location.forEach((item) => {
locationsToPush.push(locations.find((p) => p.city === item));
});
} else {
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);
}
if (queryObject.has("subcategory")) {
setSelectedSubcategory(
category?.subcategories?.find(
(item) =>
item.name.toString() === queryObject.get("subcategory").toString()
)
);
}
if (queryObject.has("location")) {
let locationsToPush = [];
// if (Array.isArray(queryObject.location)) {
queryObject.getAll("location").forEach((item) => {
locationsToPush.push(
locations.find((p) => p.city === queryObject.location)
locations.find((p) => p.city === item)
);
}
setSelectedLocations([...locationsToPush]);
}
// For future changes if needed
// if (queryObject.sortBy) {
// if (queryObject.sortBy === sortOptions.OLD.queryString) {
// setSelectedSortOption(sortOptions.OLD);
// }
// if (queryObject.sortBy === sortOptions.NEW.queryString) {
// setSelectedSortOption(sortOptions.NEW);
// }
// if (queryObject.sortBy === sortOptions.POPULAR.queryString) {
// setSelectedSortOption(sortOptions.POPULAR);
// }
});
// } else {
// locationsToPush.push(
// locations.find((p) => p.city === queryObject.location)
// );
// }
setLoadedFromQS(true);
dispatch(setIsAppliedStatus(true));
setSelectedLocations([...locationsToPush]);
}
}
}, [history.location.search]);

useEffect(() => {
if (loadedFromQS) {
makeQueryString();
}
}, [
selectedCategory,
selectedLocations,
selectedSubcategory,
loadedFromQS,
]);
}, [queryStringHook.queryString, categories, locations]);
// useEffect(() => {
// console.log("if (!loadedFromQS) {");
// console.log("if (!loadedFromQS) {", queryString);
// if (!loadedFromQS) {
// if (categories && locations) {
// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);
// console.log(queryObject)
// let category;
// if (queryObject.category) {
// category = categories.find(
// (item) => item.name === queryObject.category.toString()
// );
// setSelectedCategory(category);
// }
// if (queryObject.subcategory) {
// setSelectedSubcategory(
// category?.subcategories?.find(
// (item) =>
// item.name.toString() === queryObject.subcategory.toString()
// )
// );
// }
// if (queryObject.location) {
// let locationsToPush = [];
// if (Array.isArray(queryObject.location)) {
// queryObject.location.forEach((item) => {
// locationsToPush.push(
// locations.find((p) => p.city === item)
// );
// });
// } else {
// locationsToPush.push(
// locations.find((p) => p.city === queryObject.location)
// );
// }
// setSelectedLocations([...locationsToPush]);
// }
// setLoadedFromQS(true);
// dispatch(setIsAppliedStatus(true));
// }
// }
// }, [history.location.search]);

// Apply everything
const applyFilters = () => {
dispatch(setIsAppliedStatus(true));
dispatch(fetchOffers({ queryString: queryString }));
history.push({
pathname: HOME_PAGE,
search: convertQueryString(queryString),
});
window.scrollTo({
top: 0,
behavior: "smooth",
});
// console.log("dispatch(setIsAppliedStatus(true));");
// console.log("dispatch(setIsAppliedStatus(true));", queryString);
// dispatch(setIsAppliedStatus(true));
// dispatch(fetchOffers({ queryString: queryStringHook.getQueryString() }));
// history.push({
// pathname: HOME_PAGE,
// search: queryStringHook.getGlobalQueryString(),
// });
// window.scrollTo({
// top: 0,
// behavior: "smooth",
// });
makeQueryString();
};

// Clear function
@@ -136,48 +167,70 @@ const useFilters = () => {
// Helper function
const makeQueryString = () => {
let qsArray = [];
// if (selectedCategory && selectedCategory?._id !== 0) {
// qsArray.push("category=" + encodeURIComponent(selectedCategory.name));
// queryStringHook.appendToQueryString("category", selectedCategory.name);
qsArray.push({key: "category", value: selectedCategory?.name })
// }
// if (selectedSubcategory && selectedSubcategory?._id !== 0) {
// qsArray.push(
// "subcategory=" + encodeURIComponent(selectedSubcategory.name)
// );
// queryStringHook.appendToQueryString("subcategory", selectedSubcategory.name)
qsArray.push({key: "subcategory", value: selectedSubcategory?.name})
// }
// if (selectedLocations && selectedLocations?.length > 0) {
selectedLocations?.forEach((location) => {
// qsArray.push("location=" + encodeURIComponent(location.city));
// queryStringHook.appendToQueryString("location", location.city);
qsArray.push({key: "location", value: location?.city})
});
qsArray.push({key: "page", value: "1"})
// }
// let qsStringFromArray = "?" + qsArray.join("&");
// dispatch(setQueryString(qsStringFromArray));
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) {
qsArray.push("category=" + encodeURIComponent(selectedCategory.name));
sum++;
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
qsArray.push(
"subcategory=" + encodeURIComponent(selectedSubcategory.name)
);
sum++;
}
if (selectedLocations && selectedLocations?.length > 0) {
selectedLocations.forEach((location) => {
qsArray.push("location=" + encodeURIComponent(location.city));
});
sum += selectedLocations.length;
}
let qsStringFromArray = "?" + qsArray.join("&");
// if (queryString.length > 1) {
// dispatch(setQueryString(queryString + "&" + qsStringFromArray));
// } else {
// let queryStringEdited = new URLSearchParams(qsStringFromArray);

dispatch(setQueryString(qsStringFromArray));
// }
return sum;
};


// Setters
const setSelectedCategory = (payload) => {
if (isApplied !== false) {
dispatch(setIsAppliedStatus(false));
}
dispatch(setFilteredCategory(payload));
if (JSON.stringify(payload) !== JSON.stringify(selectedCategory)) {
dispatch(setFilteredCategory(payload));
}
};
const setSelectedSubcategory = (payload) => {
if (isApplied !== false) {
dispatch(setIsAppliedStatus(false));
}
dispatch(setFilteredSubcategory(payload));
if (JSON.stringify(payload) !== JSON.stringify(selectedSubcategory)) {
dispatch(setFilteredSubcategory(payload));
}
};
const setSelectedLocations = (payload) => {
if (isApplied !== false) {
dispatch(setIsAppliedStatus(false));
}
dispatch(setFilteredLocations(payload));
if (JSON.stringify(payload) !== JSON.stringify(selectedLocations)) {
dispatch(setFilteredLocations(payload));
}
};
return {
selectedCategory,
@@ -189,11 +242,13 @@ const useFilters = () => {
categories,
subcategories,
locations,
queryString,
applyFilters,
clearFilters,
isApplied,
makeQueryString
makeQueryString,
calculateFiltersChosen,
loadedFromQS,
setLoadedFromQS,
};
};


+ 8
- 0
src/hooks/usePaging.js Переглянути файл

@@ -0,0 +1,8 @@
import { useState } from "react"

export const usePaging = () => {
const [page, setPage] = useState();
return {
page, setPage
}
}

+ 182
- 0
src/hooks/useQueryString.js Переглянути файл

@@ -0,0 +1,182 @@
import _ from "lodash";
import { useEffect, useState } from "react";
// import _ from "lodash"
import { useDispatch, useSelector } from "react-redux";
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 useFilters from "./useFilters";
// import useSorting from "./useSorting";
// import { sortEnum } from "../enums/sortEnum";
import { convertQueryStringBackend, convertQueryStringFrontend } from "../util/helpers/queryHelpers";

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 [isFetched, setIsFetched] = useState(false);
const dispatch = useDispatch();
// const sorting = useSorting();

useEffect(() => {
const queryStringLocal = history.location.search.substring(1);
setQueryString(convertQueryStringBackend(queryStringLocal));
setGlobalQueryString(queryStringLocal);
console.log("initial bre")
}, []);

useEffect(() => {
console.log(globalQueryString);
console.log(history.location.search.substring(1))
if (globalQueryString === history.location.search.substring(1))
setLoadedFromURL(true);
// qo.delete("location")
}, [globalQueryString]);

useEffect(() => {
if (initial && loadedFromURL) {
if (queryString?.length > 0) {
const fun = _.once(() => {
setGlobalQueryString(convertQueryStringFrontend(queryString));
setInitial(false);
});
fun();
}
} else {
setGlobalQueryString(convertQueryStringFrontend(queryString));
}
}, [queryString, loadedFromURL]);

useEffect(() => {
if (!initial) {
history.push({
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);
};
const appendToQueryString = (key, value) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (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();
}
};
const appendMultipleToQueryString = (array = []) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (
array.find((item) => item.key === "category") ||
array.find((item) => item.key === "subcategory")
) {
urlParams.delete("location");
}
array.forEach((item) => {
// if (item.key === "location") {
// if (urlParams.has(item.key)) {
// let arrayOfLocations = urlParams.getAll(item.key);
// if (arrayOfLocations.includes(item.value)) {
// arrayOfLocations = arrayOfLocations.filter(
// (itemInList) =>
// itemInList?.toString() !== item?.value?.toString()
// );
// urlParams.delete(item.key);
// arrayOfLocations.forEach((location) => {
// urlParams.append(item.key, location);
// });
// }
// }
// } else {
if (urlParams.has(item.key) && item.key !== "location") {
urlParams.delete(item.key);
}
// }
console.log("item.key: ", item.key);
console.log("item.value: ", item.value);
if (!item.value) return;
urlParams.append(item.key, item.value);
console.log("querytString: ", urlParams.toString())
});
console.log("postavlja se");
setQueryString(urlParams.toString());
return urlParams.toString();
}
};
const deleteFromQueryString = (key, value = null) => {
let urlParams = new URLSearchParams(queryString);
if (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 === "sortBy") {
urlParams.delete("_des_date");
urlParams.delete("_des_popular");
} else {
urlParams.delete(key);
}
setQueryString(urlParams.toString());
return urlParams.toString();
};
const getInitialQueryString = () => {
let urlParams = new URLSearchParams(queryString);
urlParams = new URLSearchParams(appendToQueryString("size", 10));
urlParams = new URLSearchParams(appendToQueryString("page", 1));
return urlParams;
};

const getGlobalQueryString = () => {
return globalQueryString;
};

return {
queryString,
globalQueryString,
getQueryString,
setQueryString,
getQueryObject,
initial,
loadedFromURL,
appendMultipleToQueryString,
getGlobalQueryString,
appendToQueryString,
getInitialQueryString,
deleteFromQueryString,
};
};

+ 30
- 0
src/hooks/useScreenDimensions.js Переглянути файл

@@ -0,0 +1,30 @@
import { useEffect, useState } from "react";

const getScreenDimensions = () => {
const height = window.innerHeight;
const width = window.innerWidth;
return {
height,
width
}
}

const useScreenDimensions = () => {
const [screenDimensions, setScreenDimensions] = useState(getScreenDimensions());
const [width, setWidth] = useState(getScreenDimensions().width);
const [height, setHeight] = useState(getScreenDimensions().height);
useEffect(() => {
const resize = () => {
setScreenDimensions(getScreenDimensions());
setWidth(getScreenDimensions().width);
setHeight(getScreenDimensions().height);
}
window.addEventListener('resize', resize);
resize();
return () => window.removeEventListener('resize', resize);
}, [])
return {
screenDimensions, width, height
}
}
export default useScreenDimensions;

+ 12
- 8
src/hooks/useSearch.js Переглянути файл

@@ -1,14 +1,18 @@
import { useDispatch, useSelector } from "react-redux";
import { fetchOffers } from "../store/actions/offers/offersActions";
import { selectQueryString } from "../store/selectors/filtersSelectors";
// import { useDispatch } from "react-redux";
import { useQueryString } from "./useQueryString";
// import { fetchOffers } from "../store/actions/offers/offersActions";
// import { selectQueryString } from "../store/selectors/queryStringSelectors";
// import { selectQueryString } from "../store/selectors/filtersSelectors";

export const useSearch = () => {
const dispatch = useDispatch();
const queryString = useSelector(selectQueryString);
// const dispatch = useDispatch();
const queryStringHook = useQueryString();
// const queryString = useSelector(selectQueryString);
const searchOffers = (searchString) => {
const queryObject = new URLSearchParams(queryString);
queryObject.set('oname', searchString);
dispatch(fetchOffers({queryString: "?" + queryObject.toString()}));
queryStringHook.appendToQueryString("oname", searchString);
// const queryObject = new URLSearchParams(queryString);
// queryObject.set('oname', searchString);
// dispatch(fetchOffers({queryString: "?" + queryObject.toString()}));
}

return {

+ 42
- 72
src/hooks/useSorting.js Переглянути файл

@@ -1,106 +1,76 @@
import { useEffect, useState } from "react";
// import { useEffect, useState } from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../constants/pages";
// import { useHistory } from "react-router-dom";
// import { HOME_PAGE } from "../constants/pages";
import { sortEnum } from "../enums/sortEnum";
import { setFilteredSortOption} from "../store/actions/filters/filtersActions";
import { fetchOffers } from "../store/actions/offers/offersActions";
import qs from "query-string";
import {
selectQueryString,
selectSelectedSortOption,
} from "../store/selectors/filtersSelectors";
import useFilters from "./useFilters";
import { setFilteredSortOption } from "../store/actions/filters/filtersActions";
// import { fetchOffers } from "../store/actions/offers/offersActions";
// import qs from "query-string";
import { selectSelectedSortOption } from "../store/selectors/filtersSelectors";
// import useFilters from "./useFilters";
// import { convertQueryString } from "../util/helpers/queryHelpers";
import { useQueryString } from "./useQueryString";
import { convertQueryStringFrontend } from "../util/helpers/queryHelpers";

const useSorting = () => {
// Local query string is query string used for fetching data from API
const [localQueryString, setLocalQueryString] = useState("");
// Global query string is query string shown for user (more user-friendly)
const [globalQueryString, setGlobalQueryString] = useState("");

const history = useHistory();
const filters = useFilters();

const queryString = useSelector(selectQueryString);

const dispatch = useDispatch();
const selectedSortOption = useSelector(selectSelectedSortOption);
const sortOptions = sortEnum;
const queryStringHook = useQueryString();

useEffect(() => {
if (localQueryString.length > 0) {
if (queryString.length > 1) {
let queryStringEdited = new URLSearchParams(queryString);
queryStringEdited.delete('_des_date');
queryStringEdited.delete('_des_popular');
dispatch(
fetchOffers({ queryString: "?" + queryStringEdited + "&" + localQueryString })
);
history.push({
pathname: HOME_PAGE,
search: "?" + queryStringEdited + "&" + globalQueryString,
});
} else {
dispatch(fetchOffers({queryString: "?" + localQueryString}));
history.push({
pathname: HOME_PAGE,
search: "?" + globalQueryString,
});
}
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
}, [localQueryString]);

useEffect(() => {
const queryStringFromUrl = history.location.search.substring(1);
const queryObjectFromUrl = qs.parse(queryStringFromUrl);
if (queryObjectFromUrl?.sortBy) {
if (queryObjectFromUrl.sortBy === sortOptions.OLD.queryString) {
setSelectedSortOption(sortOptions.OLD)
}
if (queryObjectFromUrl.sortBy === sortOptions.NEW.queryString) {
setSelectedSortOption(sortOptions.NEW)
}
if (queryObjectFromUrl.sortBy === sortOptions.POPULAR.queryString) {
setSelectedSortOption(sortOptions.POPULAR)
if (queryStringHook.loadedFromURL) {
const queryString = queryStringHook.queryString;
let queryObject = new URLSearchParams(
convertQueryStringFrontend(queryString)
);
if (queryObject.has("sortBy"))
if (queryObject.get("sortBy") === "newest") {
setSelectedSortOption(sortEnum.NEW);
}
} else {
setSelectedSortOption(sortOptions.INITIAL)
if (queryObject.get("sortBy") === "oldest") {
setSelectedSortOption(sortEnum.OLD);
}
if (queryObject.get("sortBy") === "popular") {
setSelectedSortOption(sortEnum.POPULAR);
}
}
setGlobalQueryString(qs.stringify(queryObjectFromUrl));
filters.makeQueryString();
}, [history.location.search]);
}, [queryStringHook.queryString, queryStringHook.loadedFromURL]);

const setSelectedSortOption = (payload) => {
dispatch(setFilteredSortOption(payload));
console.log("konzola sort: ", payload)
let _des_date = null;
let _des_popular = null;
if (payload === sortOptions.NEW) {
setGlobalQueryString(`sortBy=${sortOptions.NEW.queryString}`);
if (payload.value === sortOptions.NEW.value) {
_des_date = true;
}
if (payload === sortOptions.OLD) {
setGlobalQueryString(`sortBy=${sortOptions.OLD.queryString}`);
if (payload.value === sortOptions.OLD.value) {
_des_date = false;
}
if (payload === sortOptions.POPULAR) {
setGlobalQueryString(`sortBy=${sortOptions.POPULAR.queryString}`);
if (payload.value === sortOptions.POPULAR.value) {
_des_popular = true;
}
if (_des_date !== null) {
setLocalQueryString(`_des_date=${_des_date}`);
queryStringHook.appendMultipleToQueryString([
{ key: "_des_date", value: `${_des_date}` },
{ key: "_des_popular" },
]);
}
if (_des_popular !== null) {
setLocalQueryString(`_des_popular=${_des_popular}`);
queryStringHook.appendMultipleToQueryString([
{ key: "_des_popular", value: `${_des_popular}` },
{ key: "_des_date" },
]);
}
};

return {
selectedSortOption,
setSelectedSortOption,
sortOptions,
// loadedQS
};
};
export default useSorting;

+ 3
- 0
src/i18n/resources/rs.js Переглянути файл

@@ -66,6 +66,7 @@ export default {
forgotPasswordEmail: "Email",
useDifferentEmail: "Iskoristite drugačiju lozinku.",
wrongCredentials: "Pogrešan mail ili lozinka!",
headerTitle: "Ulogujte se"
},
password: {
weak: "slaba",
@@ -75,6 +76,7 @@ export default {
},
register: {
title: "Registruj se",
headerTitle: "Registrujte se",
descriptionMain: "Trampa sa kolegama na dohvat ruke",
descriptionFirst:
"Email i Lozinka biće Vam primarni način da se ulogujete u aplikaciju",
@@ -150,5 +152,6 @@ export default {
myOffers: "Moje objave",
checkEverything: "POGLEDAJ SVE",
myMessages: "Moje poruke",
newOffers: "Najnovije ponude"
},
};

+ 0
- 44
src/pages/HomePage/HomePageMUI.js Переглянути файл

@@ -15,50 +15,6 @@ import MainLayout from "../../layouts/MainLayout/MainLayout";
import MarketPlace from "../../components/MarketPlace/MarketPlace";

const HomePage = () => {
// const dispatch = useDispatch();

// const history = useHistory();
// useEffect(() => {
// let category = null, subcategory = null, cities = [], _des_date = false, _des_popular = false, page, size;
// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);

// if (queryObject.category) {
// category = Mockupdata[1].find(
// (item) => item.string === queryObject.category.toString()
// ).id;
// }
// if (queryObject.subcategory) {
// subcategory = Mockupdata[1].find(
// (item) => item.string === queryObject.subcategory.toString()
// ).id;
// }
// if (queryObject.city) {
// if (Array.isArray(queryObject.city)) {
// queryObject.city.forEach((item) => {
// cities.push(Mockupdata[0].find((p) => p.string === item).id);
// });
// } else {
// cities.push(
// Mockupdata[0].find((p) => p.string === queryObject.city).id
// );
// }
// }
// if (queryObject.sortBy) {
// if (queryObject.sortBy === "dateAsc") _des_date = false;
// if (queryObject.sortBy === "dateDesc") _des_date = true;
// if (queryObject.sortBy === "popular") _des_popular = true;
// }
// if (queryObject.page) {
// page = queryObject.page;
// }
// if (queryObject.size) {
// size = queryObject.size;
// }

// dispatch(setFilters({ category, subcategory, cities, _des_date, _des_popular, page, size }));
// }, [history.location.search]);

return (
<HomePageContainer>
<MainLayout leftCard={<FilterCard />} content={<MarketPlace />} />

+ 2
- 2
src/pages/RegisterPages/Register/Register.styled.js Переглянути файл

@@ -12,7 +12,7 @@ export const RegisterPageContainer = styled(Container)`
width: 335px;
padding: 0;
flex: 1;
position: relative;
/* position: relative; */
transition: 1s all;
${props => props.currentstep === 3 && `margin-top: 40px`};
@media (max-height: 900px) {
@@ -83,7 +83,7 @@ export const ProgressContainer = styled(Container)`
padding: 0;
`;
export const Footer = styled(Box)`
position: absolute;
position: relative;
bottom: 36px;
display: flex;
width: 100%;

+ 2
- 1
src/request/apiEndpoints.js Переглянути файл

@@ -162,6 +162,7 @@ export default {
getOffers: 'offers',
addOffer: 'offers',
categories: 'categories',
locations: 'locations'
locations: 'locations',
mineOffers: 'users'
}
};

+ 3
- 0
src/request/chatRequest.js Переглянути файл

@@ -2,4 +2,7 @@ import { getRequest } from "."

export const attemptFetchChats = (payload) => {
return getRequest(`users/${payload}/chat`);
}
export const attemptFetchHeaderChats = (payload) => {
return getRequest(`users/${payload}/chat/?page=1&size=2`)
}

+ 5
- 2
src/request/offersRequest.js Переглянути файл

@@ -2,8 +2,8 @@ import { getRequest, postRequest } from "."
import apiEndpoints from "./apiEndpoints"

export const attemptFetchOffers = (payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload + "&size=10&page=1")
return getRequest(apiEndpoints.offers.getOffers + "?size=10&page=1")
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload)
return getRequest(apiEndpoints.offers.getOffers)
}
export const attemptFetchMoreOffers = (page, payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload + `&size=10&page=${page}`);
@@ -11,4 +11,7 @@ export const attemptFetchMoreOffers = (page, payload) => {
}
export const attemptAddOffer = (payload) => {
return postRequest(apiEndpoints.offers.addOffer, payload)
}
export const attemptFetchMineOffers = (payload) => {
return getRequest(`${apiEndpoints.offers.mineOffers}/${payload}/offers`)
}

+ 2
- 0
src/store/actions/chat/chatActionConstants.js Переглянути файл

@@ -1,7 +1,9 @@
import { createFetchType } from "../actionHelpers";

const CHAT_SCOPE = "CHAT_SCOPE";
const CHAT_HEADER_SCOPE = "CHAT_HEADER_SCOPE";

export const CHAT_FETCH = createFetchType(CHAT_SCOPE);
export const CHAT_HEADER_FETCH = createFetchType(CHAT_HEADER_SCOPE);

export const CHAT_SET = "CHAT_SET";

+ 5
- 1
src/store/actions/chat/chatActions.js Переглянути файл

@@ -1,9 +1,13 @@
import { CHAT_FETCH, CHAT_SET } from "./chatActionConstants";
import { CHAT_FETCH, CHAT_HEADER_FETCH, CHAT_SET } from "./chatActionConstants";

export const fetchChats = (payload) => ({
type: CHAT_FETCH,
payload,
})
export const fetchHeaderChats = (payload) => ({
type: CHAT_HEADER_FETCH,
payload,
})
export const setChats = (payload) => ({
type: CHAT_SET,
payload,

+ 2
- 1
src/store/actions/login/loginActions.js Переглянути файл

@@ -46,8 +46,9 @@ export const authenticateUser = () => ({
type: AUTHENTICATE_USER,
});

export const logoutUser = () => ({
export const logoutUser = (payload) => ({
type: LOGOUT_USER,
payload,
});

export const refreshUserToken = (payload) => ({

+ 5
- 1
src/store/actions/offers/offersActionConstants.js Переглянути файл

@@ -5,6 +5,8 @@ const OFFERS_SCOPE = "OFFERS_SCOPE";
const OFFERS_MORE_SCOPE = "OFFERS_MORE_SCOPE";
export const OFFERS_FETCH_MORE = createFetchType(OFFERS_MORE_SCOPE);

const OFFERS_MINE_SCOPE = "OFFERS_MINE_SCOPE";
export const OFFERS_MINE_FETCH = createFetchType(OFFERS_MINE_SCOPE);
export const OFFERS_FETCH = createFetchType(OFFERS_SCOPE);
export const OFFERS_SUCCESS = createSuccessType(OFFERS_SCOPE);
export const OFFERS_ERROR = createErrorType(OFFERS_SCOPE);
@@ -16,4 +18,6 @@ export const OFFERS_PINNED_SET = "OFFERS_PINNED_SET";
export const OFFERS_PINNED_ADD = "OFFERS_PINNED_ADD";
export const OFFERS_SET = "OFFERS_SET";
export const OFFERS_ADD = "OFFERS_ADD";
export const OFFERS_NO_MORE = "OFFERS_NO_MORE";
export const OFFERS_NO_MORE = "OFFERS_NO_MORE";
export const OFFERS_SET_TOTAL = "OFFERS_SET_TOTAL";
export const OFFERS_MINE_SET = "OFFERS_MY_ADD";

+ 55
- 30
src/store/actions/offers/offersActions.js Переглянути файл

@@ -1,41 +1,66 @@
import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_FETCH_MORE, OFFERS_NO_MORE, OFFERS_PINNED_ADD, OFFERS_PINNED_SET, OFFERS_SET, OFFERS_SUCCESS} from "./offersActionConstants";
import {
OFFERS_ADD,
OFFERS_CLEAR,
OFFERS_ERROR,
OFFERS_FETCH,
OFFERS_FETCH_MORE,
OFFERS_MINE_FETCH,
OFFERS_MINE_SET,
OFFERS_NO_MORE,
OFFERS_PINNED_ADD,
OFFERS_PINNED_SET,
OFFERS_SET,
OFFERS_SET_TOTAL,
OFFERS_SUCCESS,
} from "./offersActionConstants";

export const fetchOffers = (payload) => ({
type: OFFERS_FETCH,
payload,
})
type: OFFERS_FETCH,
payload,
});
export const fetchOffersSuccess = (payload) => ({
type: OFFERS_SUCCESS,
payload
})
type: OFFERS_SUCCESS,
payload,
});
export const fetchOffersError = (payload) => ({
type: OFFERS_ERROR,
payload,
})
type: OFFERS_ERROR,
payload,
});
export const clearOffers = () => ({
type: OFFERS_CLEAR,
})
type: OFFERS_CLEAR,
});
export const setOffers = (payload) => ({
type: OFFERS_SET,
payload,
})
type: OFFERS_SET,
payload,
});
export const setPinnedOffers = (payload) => ({
type: OFFERS_PINNED_SET,
payload
})
type: OFFERS_PINNED_SET,
payload,
});
export const addPinnedOffers = (payload) => ({
type: OFFERS_PINNED_ADD,
payload,
})
type: OFFERS_PINNED_ADD,
payload,
});
export const addOffers = (payload) => ({
type: OFFERS_ADD,
payload
})
type: OFFERS_ADD,
payload,
});
export const fetchMoreOffers = (payload) => ({
type: OFFERS_FETCH_MORE,
payload
})
type: OFFERS_FETCH_MORE,
payload,
});
export const setNoMoreOffersStatus = (payload) => ({
type: OFFERS_NO_MORE,
payload
})
type: OFFERS_NO_MORE,
payload,
});
export const setTotalOffers = (payload) => ({
type: OFFERS_SET_TOTAL,
payload,
});
export const fetchMineOffers = () => ({
type: OFFERS_MINE_FETCH,
});
export const setMineOffers = (payload) => ({
type: OFFERS_MINE_SET,
payload,
});

+ 2
- 0
src/store/actions/queryString/queryStringActionConstants.js Переглянути файл

@@ -0,0 +1,2 @@
export const QUERY_STRING_SET = "QUERY_STRING_SET";
export const QUERY_STRING_SET_REDUX = "QUERY_STRING_SET_REDUX";

+ 10
- 0
src/store/actions/queryString/queryStringActions.js Переглянути файл

@@ -0,0 +1,10 @@
import { QUERY_STRING_SET, QUERY_STRING_SET_REDUX } from "./queryStringActionConstants"

export const setQueryStringRedux = (payload) => ({
type: QUERY_STRING_SET_REDUX,
payload,
})
export const setQueryString = (payload) => ({
type: QUERY_STRING_SET,
payload,
})

+ 0
- 11
src/store/reducers/filters/filtersReducer.js Переглянути файл

@@ -4,7 +4,6 @@ import {
SET_FILTERS,
SET_IS_APPLIED,
SET_LOCATIONS,
SET_QUERY_STRING,
SET_SORT_OPTION,
SET_SUBCATEGORY,
} from "../../actions/filters/filtersActionConstants";
@@ -30,7 +29,6 @@ export default createReducer(
[SET_LOCATIONS]: setFilteredLocations,
[SET_SORT_OPTION]: setFilteredSortOption,
[SET_IS_APPLIED]: setIsAppliedStatus,
[SET_QUERY_STRING]: setQueryString,
},
initialState
);
@@ -92,13 +90,4 @@ function setIsAppliedStatus(state, {payload}) {
isApplied: payload,
}
}
}
function setQueryString(state, {payload}) {
return {
...state,
filters: {
...state.filters,
queryString: payload,
}
}
}

+ 3
- 1
src/store/reducers/index.js Переглянути файл

@@ -12,6 +12,7 @@ import categoriesReducer from "./categories/categoriesReducer";
import locationsReducer from "./locations/locationsReducer";
import profileReducer from "./profile/profileReducer";
import chatReducer from "./chat/chatReducer";
import queryStringReducer from "./queryString/queryStringReducer";

const loginPersistConfig = {
key: "login",
@@ -73,5 +74,6 @@ export default combineReducers({
categories: persistReducer(categoriesPersistConfig, categoriesReducer),
locations: persistReducer(locationsPersistConfig, locationsReducer),
profile: persistReducer(profilePersistConfig, profileReducer),
chat: persistReducer(chatPersistConfig, chatReducer)
chat: persistReducer(chatPersistConfig, chatReducer),
queryString: queryStringReducer,
});

+ 18
- 0
src/store/reducers/offers/offersReducer.js Переглянути файл

@@ -2,16 +2,20 @@ import {
OFFERS_ADD,
OFFERS_CLEAR,
OFFERS_ERROR,
OFFERS_MINE_SET,
OFFERS_NO_MORE,
OFFERS_PINNED_ADD,
OFFERS_PINNED_SET,
OFFERS_SET,
OFFERS_SET_TOTAL,
} from "../../actions/offers/offersActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
offers: [],
pinnedOffers: [],
mineOffers: [],
total: 0,
error: "",
newOffer: "",
noMoreOffers: false,
@@ -26,6 +30,8 @@ export default createReducer(
[OFFERS_NO_MORE]: setNoMoreOffersStatus,
[OFFERS_PINNED_ADD]: addPinnedOffers,
[OFFERS_PINNED_SET]: setPinnedOffers,
[OFFERS_SET_TOTAL]: setTotalOffers,
[OFFERS_MINE_SET]: setMineOffers
},
initialState
);
@@ -67,3 +73,15 @@ function setNoMoreOffersStatus(state, action) {
noMoreOffers: action.payload
}
}
function setTotalOffers(state, action) {
return {
...state,
total: action.payload
}
}
function setMineOffers(state, action) {
return {
...state,
mineOffers: action.payload
}
}

+ 20
- 0
src/store/reducers/queryString/queryStringReducer.js Переглянути файл

@@ -0,0 +1,20 @@
import { QUERY_STRING_SET_REDUX } from "../../actions/queryString/queryStringActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
queryString: "",
};

export default createReducer(
{
[QUERY_STRING_SET_REDUX]: setQueryStringRedux,
},
initialState
);

function setQueryStringRedux(state, action) {
return {
...state,
queryString: action.payload,
};
}

+ 13
- 3
src/store/saga/chatSaga.js Переглянути файл

@@ -1,6 +1,6 @@
import { all, takeLatest, call, put } from "@redux-saga/core/effects";
import { attemptFetchChats } from "../../request/chatRequest";
import { CHAT_FETCH } from "../actions/chat/chatActionConstants";
import { attemptFetchChats, attemptFetchHeaderChats } from "../../request/chatRequest";
import { CHAT_FETCH, CHAT_HEADER_FETCH } from "../actions/chat/chatActionConstants";
import { setChats } from "../actions/chat/chatActions";

function* fetchChats(payload) {
@@ -13,8 +13,18 @@ function* fetchChats(payload) {
}
}

function* fetchHeaderChats(payload) {
try {
const data = yield call(attemptFetchHeaderChats, payload.payload);
yield put(setChats(data.data));
} catch (e) {
console.log(e);
}
}

export default function* chatSaga() {
yield all([
takeLatest(CHAT_FETCH, fetchChats)
takeLatest(CHAT_FETCH, fetchChats),
takeLatest(CHAT_HEADER_FETCH, fetchHeaderChats)
]);
}

+ 3
- 1
src/store/saga/index.js Переглянути файл

@@ -6,6 +6,7 @@ import locationsSaga from './locationsSaga';
import loginSaga from './loginSaga';
import offersSaga from './offersSaga';
import profileSaga from './profileSaga';
import queryStringSaga from './queryStringSaga';
import registerSaga from './registerSaga';

export default function* rootSaga() {
@@ -17,6 +18,7 @@ export default function* rootSaga() {
categoriesSaga(),
locationsSaga(),
profileSaga(),
chatSaga()
chatSaga(),
queryStringSaga(),
]);
}

+ 7
- 3
src/store/saga/loginSaga.js Переглянути файл

@@ -95,16 +95,20 @@ function* authenticateUser() {
}
}

function* logout() {
function* logout(payload) {
try {
const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN);
const user = jwt.decode(JwtToken);
const user = yield call(jwt.decode, JwtToken);
console.log(payload);
if (user) {
yield call(logoutUserRequest, { token: JwtToken, userId: user._id });
const requestBody = {token: JwtToken}
yield call(logoutUserRequest, requestBody);
yield call(payload.payload)
}
} catch (error) {
console.log(error); // eslint-disable-line
} finally {
console.log('ovde');
yield call(authScopeClearHelper);
yield call(removeHeaderToken);
yield put(resetLoginState());

+ 57
- 23
src/store/saga/offersSaga.js Переглянути файл

@@ -1,40 +1,57 @@
import { all, takeLatest, call, put } from "@redux-saga/core/effects";
import { all, takeLatest, call, put, select } from "@redux-saga/core/effects";
import {
attemptFetchMineOffers,
attemptFetchMoreOffers,
attemptFetchOffers,
} from "../../request/offersRequest";
import { setQueryString } from "../actions/filters/filtersActions";
import { convertQueryStringBackend } from "../../util/helpers/queryHelpers";
// import { setQueryString } from "../actions/filters/filtersActions";
import {
OFFERS_FETCH,
OFFERS_FETCH_MORE,
OFFERS_MINE_FETCH,
} from "../actions/offers/offersActionConstants";
import {
addOffers,
addPinnedOffers,
clearOffers,
setMineOffers,
setNoMoreOffersStatus,
setOffers,
setPinnedOffers,
setTotalOffers,
} from "../actions/offers/offersActions";
import { selectUserId } from "../selectors/loginSelectors";
import {
selectOffers,
selectPinnedOffers,
selectTotalOffers,
} from "../selectors/offersSelectors";

function* fetchOffers(payload) {
try {
yield put(clearOffers());
yield put(setNoMoreOffersStatus(false));
const data = yield call(attemptFetchOffers, payload.payload.queryString);
if (
data.data.items.pinnedOffers.length +
data.data.items.regularOffers.length <
10 ||
data.data.items.pinnedOffers.length +
data.data.items.regularOffers.length >
data.data.total
) {
yield put(setNoMoreOffersStatus(true));
}
if (payload.payload.queryString) {
yield put(setQueryString(payload.payload.queryString));
}

// adding page and size to query string
// let newQueryString = new URLSearchParams(payload.payload.queryString);
// if (!newQueryString.has('page')) {
// newQueryString.append('page', 1);
// if (newQueryString.has('size')) newQueryString.delete('size');
// newQueryString.append('size', 10);
// }
const newQueryString = new URLSearchParams(
convertQueryStringBackend(payload.payload.queryString)
);
console.log("fetch offers: ", newQueryString.toString());
const data = yield call(
attemptFetchOffers,
"?" + newQueryString.toString()
);
console.log("if (payload.payload.queryString) {");
// if (payload.payload.queryString) {
// yield put(setQueryString(payload.payload.queryString));
// }
yield put(setTotalOffers(data.data.total));
yield put(setOffers(data.data.items.regularOffers));
yield put(setPinnedOffers(data.data.items.pinnedOffers));
} catch (e) {
@@ -48,14 +65,20 @@ function* fetchMoreOffers(payload) {
payload.payload?.page,
payload.payload?.queryString
);
if (payload.payload.queryString) {
yield put(setQueryString(payload.payload.queryString));
// if (payload.payload.queryString) {
// yield put(setQueryString(payload.payload.queryString));
// }
const oldOffers = yield select(selectOffers);
const oldPinnedOffers = yield select(selectPinnedOffers);
const totalNumberOfOffers = yield select(selectTotalOffers);
if (oldOffers.length + oldPinnedOffers.length < totalNumberOfOffers) {
yield put(addOffers(data.data.items.regularOffers));
yield put(addPinnedOffers(data.data.items.pinnedOffers));
}
console.log(data.data.items);
yield put(addOffers(data.data.items.regularOffers));
yield put(addPinnedOffers(data.data.items.pinnedOffers));
if (
data.data.items.pinnedOffers + data.data.items.regularOffers < 10 ||
data.data.items.pinnedOffers.length +
data.data.items.regularOffers.length <
10 ||
data.data.items.pinnedOffers.length +
data.data.items.regularOffers.length >
data.data.total
@@ -67,9 +90,20 @@ function* fetchMoreOffers(payload) {
}
}

function* fetchMineOffers() {
try {
const userId = yield select(selectUserId);
const data = yield call(attemptFetchMineOffers, userId);
yield put(setMineOffers(data.data));
} catch (e) {
console.log(e);
}
}

export default function* offersSaga() {
yield all([
takeLatest(OFFERS_FETCH, fetchOffers),
takeLatest(OFFERS_FETCH_MORE, fetchMoreOffers),
takeLatest(OFFERS_MINE_FETCH, fetchMineOffers),
]);
}

+ 30
- 0
src/store/saga/queryStringSaga.js Переглянути файл

@@ -0,0 +1,30 @@
import { all, takeLatest, put } from "@redux-saga/core/effects";
import { convertQueryStringBackend } from "../../util/helpers/queryHelpers";
// import { combineQueryStrings } from "../../util/helpers/queryHelpers";
import { QUERY_STRING_SET } from "../actions/queryString/queryStringActionConstants";
import { setQueryStringRedux } from "../actions/queryString/queryStringActions";
// import { selectQueryString } from "../selectors/queryStringSelectors";

function* setQueryString(payload) {
try {
console.log("trenutni queryString: ", payload.payload);
console.log(payload);
// const currentQS = yield select(selectQueryString);
// let newQueryString = payload.payload
// if (currentQS?.length > 0) {
// newQueryString = yield call(
// combineQueryStrings,
// currentQS,
// payload.payload,
// );
// }
const newQueryString = convertQueryStringBackend(payload.payload)
yield put(setQueryStringRedux(newQueryString));
} catch (e) {
console.log(e);
}
}

export default function* queryStringSaga() {
yield all([takeLatest(QUERY_STRING_SET, setQueryString)]);
}

+ 0
- 4
src/store/selectors/filtersSelectors.js Переглянути файл

@@ -26,7 +26,3 @@ export const selectAppliedStatus = createSelector(
filtersSelector,
(state) => state.filters.isApplied
)
export const selectQueryString = createSelector(
filtersSelector,
(state) => state.filters.queryString
)

+ 8
- 0
src/store/selectors/offersSelectors.js Переглянути файл

@@ -19,3 +19,11 @@ export const selectPinnedOffers = createSelector(
offersSelector,
(state) => state.pinnedOffers
)
export const selectTotalOffers = createSelector(
offersSelector,
(state) => state.total
)
export const selectMineOffers = createSelector(
offersSelector,
(state) => state.mineOffers
)

+ 8
- 0
src/store/selectors/queryStringSelectors.js Переглянути файл

@@ -0,0 +1,8 @@
import { createSelector } from "reselect";

const queryStringSelector = (state) => state.queryString;

export const selectQueryString = createSelector(
queryStringSelector,
(state => state.queryString)
)

+ 0
- 2
src/util/helpers/authScopeHelpers.js Переглянути файл

@@ -10,10 +10,8 @@ export function authScopeGetHelper(key) {

export function authScopeStringGetHelper(key) {
if (sessionStorage.getItem(SESSION_STORAGE_SCOPE)) {
console.log(sessionStorage.getItem(key))
return sessionStorage.getItem(key);
}
console.log(localStorage.getItem(key))

return localStorage.getItem(key);
}

+ 119
- 15
src/util/helpers/queryHelpers.js Переглянути файл

@@ -1,19 +1,123 @@
import { sortEnum } from "../../enums/sortEnum";
// import qs from "query-string";

export const convertQueryString = (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);
} else {
queryObjectToReturn.append('sortBy', sortEnum.OLD.queryString);
}
export const convertQueryStringFrontend = (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);
} else {
queryObjectToReturn.append("sortBy", sortEnum.OLD.queryString);
}
if (queryObject.has('_des_popular')) {
queryObjectToReturn.delete('_des_popular');
queryObjectToReturn.append('sortBy', sortEnum.POPULAR.queryString);
}
if (queryObject.has("oname")) {
queryObjectToReturn.delete("oname");
queryObjectToReturn.append("search", queryObject.get("oname"));
}
if (queryObject.has("_des_popular")) {
queryObjectToReturn.delete("_des_popular");
queryObjectToReturn.append("sortBy", sortEnum.POPULAR.queryString);
}
if (queryObject.has("size")) {
queryObjectToReturn.delete("size");
}
if (queryObject.has("page")) {
if (queryObject.get("page") === "1") {
queryObjectToReturn.delete("page");
return queryObjectToReturn.toString();
} else {
queryObjectToReturn.delete("page");
return (
queryObjectToReturn.toString() + "&page=" + queryObject.get("page")
);
}
return queryObjectToReturn.toString();
}
}
return queryObjectToReturn.toString();
};

export const combineQueryStrings = (firstQuery, secondQuery) => {
const firstQueryObject = new URLSearchParams(firstQuery);
const secondQueryObject = new URLSearchParams(secondQuery);
const thirdQueryObject = new URLSearchParams(secondQueryObject.toString());
let arrayOfProperties = Object.getOwnPropertyNames(
Object.fromEntries(firstQueryObject)
);
arrayOfProperties.forEach((property) => {
console.log("firstQueryObject[property]: ", firstQueryObject.toString());
console.log("secondQueryObject[property]", secondQueryObject.toString());
if (!secondQueryObject.has(property)) {
// console.log("ovde je doslo query: ", property);
// thirdQueryObject.append(property, secondQueryObject.get(property));
thirdQueryObject.append(property, firstQueryObject.get(property));
} else {
// console.log("ovde ispod: ", property);
}
});
console.log("thirdQueryObject[property]: ", thirdQueryObject.toString());

return thirdQueryObject.toString();
};

export const convertQueryStringBackend = (queryURL) => {
const queryObject = new URLSearchParams(queryURL);
const newQueryObject = new URLSearchParams();
if (queryObject.has("category")) {
newQueryObject.append(
"category",
queryObject.getAll("category")[queryObject.getAll("category").length - 1]
);
}
if (queryObject.has("subcategory")) {
newQueryObject.append(
"subcategory",
queryObject.getAll("subcategory")[
queryObject.getAll("subcategory").length - 1
]
);
}
if (queryObject.has("search")) {
newQueryObject.append(
"oname",
queryObject.getAll("search")[queryObject.getAll("search").length - 1]
);
}
if (queryObject.has("oname")) {
newQueryObject.append(
"oname",
queryObject.getAll("oname")[queryObject.getAll("oname").length - 1]
);
}
if (queryObject.has("location")) {
const arrayOfLocations = queryObject.getAll("location");
arrayOfLocations.forEach((item) => {
newQueryObject.append("location", item);
});
}
if (queryObject.has("sortBy")) {
newQueryObject.delete("sortBy");
if (queryObject.get("sortBy") === "newest") {
newQueryObject.append("_des_date", "true");
}
if (queryObject.get("sortBy") === "oldest") {
newQueryObject.append("_des_date", "false");
}
if (queryObject.get("sortBy") === "popular") {
newQueryObject.append("_des_popular", "true");
}
}
if (queryObject.has("_des_date")) {
newQueryObject.append("_des_date", queryObject.get("_des_date"));
}
if (queryObject.has("_des_popular")) {
newQueryObject.append("_des_popular", queryObject.get("_des_popular"));
}
newQueryObject.append("size", "10");
if (!queryObject.has("page")) {
newQueryObject.append("page", "1");
} else {
newQueryObject.append("page", queryObject.get("page"));
}
return newQueryObject.toString();
};

Завантаження…
Відмінити
Зберегти