| @@ -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" | |||
| } | |||
| } | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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> | |||
| )}; | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -3,7 +3,7 @@ body { | |||
| -webkit-font-smoothing: antialiased; | |||
| -moz-osx-font-smoothing: grayscale; | |||
| overflow-anchor: none; | |||
| background-color: #F1F1F1; | |||
| background-color: #F5F5F5; | |||
| } | |||
| * { | |||
| @@ -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; | |||
| @@ -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`}; | |||
| `; | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| } | |||
| `; | |||
| @@ -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; | |||
| @@ -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)``; | |||
| @@ -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 | |||
| @@ -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> | |||
| @@ -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; | |||
| } | |||
| ` | |||
| @@ -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; | |||
| } | |||
| `; | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| ` | |||
| @@ -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; | |||
| @@ -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"; | |||
| ` | |||
| @@ -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")} | |||
| /> | |||
| ); | |||
| @@ -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")} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -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} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -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)` | |||
| ` | |||
| @@ -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; | |||
| @@ -13,7 +13,6 @@ export const TextField = React.forwardRef((props, ref) => { | |||
| setIsFieldEmpty(false); | |||
| } | |||
| }, [props.value]); | |||
| return ( | |||
| <TextFieldContainer | |||
| style={props.containerStyle} | |||
| @@ -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, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,8 @@ | |||
| import { useState } from "react" | |||
| export const usePaging = () => { | |||
| const [page, setPage] = useState(); | |||
| return { | |||
| page, setPage | |||
| } | |||
| } | |||
| @@ -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, | |||
| }; | |||
| }; | |||
| @@ -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; | |||
| @@ -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 { | |||
| @@ -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; | |||
| @@ -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" | |||
| }, | |||
| }; | |||
| @@ -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 />} /> | |||
| @@ -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%; | |||
| @@ -162,6 +162,7 @@ export default { | |||
| getOffers: 'offers', | |||
| addOffer: 'offers', | |||
| categories: 'categories', | |||
| locations: 'locations' | |||
| locations: 'locations', | |||
| mineOffers: 'users' | |||
| } | |||
| }; | |||
| @@ -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`) | |||
| } | |||
| @@ -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`) | |||
| } | |||
| @@ -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"; | |||
| @@ -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, | |||
| @@ -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,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"; | |||
| @@ -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, | |||
| }); | |||
| @@ -0,0 +1,2 @@ | |||
| export const QUERY_STRING_SET = "QUERY_STRING_SET"; | |||
| export const QUERY_STRING_SET_REDUX = "QUERY_STRING_SET_REDUX"; | |||
| @@ -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, | |||
| }) | |||
| @@ -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, | |||
| } | |||
| } | |||
| } | |||
| @@ -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, | |||
| }); | |||
| @@ -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 | |||
| } | |||
| } | |||
| @@ -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, | |||
| }; | |||
| } | |||
| @@ -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) | |||
| ]); | |||
| } | |||
| @@ -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(), | |||
| ]); | |||
| } | |||
| @@ -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()); | |||
| @@ -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), | |||
| ]); | |||
| } | |||
| @@ -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)]); | |||
| } | |||
| @@ -26,7 +26,3 @@ export const selectAppliedStatus = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.isApplied | |||
| ) | |||
| export const selectQueryString = createSelector( | |||
| filtersSelector, | |||
| (state) => state.filters.queryString | |||
| ) | |||
| @@ -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 | |||
| ) | |||
| @@ -0,0 +1,8 @@ | |||
| import { createSelector } from "reselect"; | |||
| const queryStringSelector = (state) => state.queryString; | |||
| export const selectQueryString = createSelector( | |||
| queryStringSelector, | |||
| (state => state.queryString) | |||
| ) | |||
| @@ -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); | |||
| } | |||
| @@ -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(); | |||
| }; | |||