Przeglądaj źródła

Merged with market place

feature/code-cleanup-joca
Djordje Mitrovic 3 lat temu
rodzic
commit
0e24efe02b
100 zmienionych plików z 3285 dodań i 772 usunięć
  1. 15
    15
      package-lock.json
  2. 13
    15
      src/App.js
  3. 4
    3
      src/AppRoutes.js
  4. 7
    0
      src/assets/images/logo.svg
  5. 7
    0
      src/assets/images/logo_vertical.svg
  6. 3
    0
      src/assets/images/svg/filter.svg
  7. 5
    0
      src/assets/images/svg/log-out.svg
  8. 43
    0
      src/assets/images/svg/logo-horizontal.svg
  9. 1
    1
      src/assets/styles/_base.scss
  10. 1
    1
      src/components/Buttons/PrimaryButton/PrimaryButton.js
  11. 4
    2
      src/components/Buttons/PrimaryButton/PrimaryButton.styled.js
  12. 2
    2
      src/components/Cards/CreateOfferCard/CreateOffer.js
  13. 24
    4
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  14. 0
    3
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  15. 64
    3
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  16. 49
    3
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js
  17. 89
    108
      src/components/Cards/FilterCard/FilterCard.js
  18. 25
    5
      src/components/Cards/FilterCard/FilterCard.styled.js
  19. 31
    28
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  20. 54
    20
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  21. 22
    15
      src/components/Cards/OfferCard/OfferCard.js
  22. 92
    12
      src/components/Cards/OfferCard/OfferCard.styled.js
  23. 1
    1
      src/components/CheckBox/CheckBox.js
  24. 1
    1
      src/components/CheckBox/Label.js
  25. 28
    8
      src/components/Dropdown/DropdownList/DropdownList.js
  26. 32
    8
      src/components/Dropdown/DropdownList/DropdownList.styled.js
  27. 458
    0
      src/components/Header/Header.js
  28. 216
    0
      src/components/Header/Header.styled.js
  29. 2
    1
      src/components/Icon/IconWithNumber/IconWithNumber.js
  30. 57
    57
      src/components/MUI/Examples/ModalsExample.js
  31. 0
    160
      src/components/MUI/NavbarComponent.js
  32. 0
    35
      src/components/MUI/PopoverComponent.js
  33. 74
    72
      src/components/MarketPlace/Header/Header.js
  34. 82
    45
      src/components/MarketPlace/Header/Header.styled.js
  35. 2
    2
      src/components/MarketPlace/MarketPlace.styled.js
  36. 112
    15
      src/components/MarketPlace/Offers/Offers.js
  37. 3
    0
      src/components/MarketPlace/Offers/Offers.styled.js
  38. 76
    0
      src/components/Paging/Paging.js
  39. 137
    0
      src/components/Paging/Paging.styled.js
  40. 82
    0
      src/components/Popovers/HeaderPopover/HeaderPopover.js
  41. 70
    0
      src/components/Popovers/HeaderPopover/HeaderPopover.styled.js
  42. 42
    0
      src/components/Popovers/MyMessages/MyMessages.js
  43. 18
    0
      src/components/Popovers/MyMessages/MyMessages.styled.js
  44. 74
    0
      src/components/Popovers/MyPosts/MyPosts.js
  45. 29
    0
      src/components/Popovers/MyPosts/MyPosts.styled.js
  46. 62
    0
      src/components/Popovers/MyProfile/MyProfile.js
  47. 34
    0
      src/components/Popovers/MyProfile/MyProfile.styled.js
  48. 32
    0
      src/components/Popovers/PopoverComponent.js
  49. 1
    1
      src/components/Radio/Button/RadioButton.js
  50. 6
    1
      src/components/Router/PrivateRoute.js
  51. 0
    1
      src/components/Scroller/HorizontalScroller.styled.js
  52. 6
    2
      src/components/Select/Option/Option.js
  53. 12
    4
      src/components/Select/Select.js
  54. 8
    2
      src/components/Select/Select.styled.js
  55. 11
    7
      src/components/TextFields/TextField/TextField.js
  56. 17
    0
      src/enums/conditionEnum.js
  57. 22
    0
      src/enums/sortEnum.js
  58. 255
    0
      src/hooks/useFilters.js
  59. 8
    0
      src/hooks/usePaging.js
  60. 182
    0
      src/hooks/useQueryString.js
  61. 30
    0
      src/hooks/useScreenDimensions.js
  62. 21
    0
      src/hooks/useSearch.js
  63. 76
    0
      src/hooks/useSorting.js
  64. 13
    1
      src/i18n/resources/rs.js
  65. 1
    0
      src/layouts/MainLayout/MainLayout.styled.js
  66. 1
    0
      src/layouts/ProfileLayout/ProfileLayout.styled.js
  67. 1
    1
      src/pages/ForgotPasswordPage/ForgotPassword.styled.js
  68. 8
    53
      src/pages/HomePage/HomePageMUI.js
  69. 1
    0
      src/pages/ItemDetailsPage/ItemDetailsPage.styled.js
  70. 2
    2
      src/pages/ItemDetailsPage/ItemDetailsPageMUI.js
  71. 7
    4
      src/pages/LoginPage/LoginPage.js
  72. 0
    3
      src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js
  73. 2
    2
      src/pages/RegisterPages/Register/Register.styled.js
  74. 6
    3
      src/request/apiEndpoints.js
  75. 5
    0
      src/request/categoriesRequest.js
  76. 8
    0
      src/request/chatRequest.js
  77. 4
    2
      src/request/index.js
  78. 5
    0
      src/request/locationsRequest.js
  79. 9
    1
      src/request/offersRequest.js
  80. 5
    0
      src/request/profileRequest.js
  81. 6
    0
      src/store/actions/categories/categoriesActionConstants.js
  82. 10
    0
      src/store/actions/categories/categoriesActions.js
  83. 9
    0
      src/store/actions/chat/chatActionConstants.js
  84. 14
    0
      src/store/actions/chat/chatActions.js
  85. 4
    1
      src/store/actions/filters/filtersActionConstants.js
  86. 17
    5
      src/store/actions/filters/filtersActions.js
  87. 6
    0
      src/store/actions/locations/locationsActionConstants.js
  88. 10
    0
      src/store/actions/locations/locationsActions.js
  89. 3
    2
      src/store/actions/login/loginActions.js
  90. 13
    1
      src/store/actions/offers/offersActionConstants.js
  91. 63
    16
      src/store/actions/offers/offersActions.js
  92. 8
    0
      src/store/actions/profile/profileActionConstants.js
  93. 19
    0
      src/store/actions/profile/profileActions.js
  94. 2
    0
      src/store/actions/queryString/queryStringActionConstants.js
  95. 10
    0
      src/store/actions/queryString/queryStringActions.js
  96. 5
    3
      src/store/middleware/accessTokensMiddleware.js
  97. 20
    0
      src/store/reducers/categories/categoriesReducer.js
  98. 20
    0
      src/store/reducers/chat/chatReducer.js
  99. 34
    9
      src/store/reducers/filters/filtersReducer.js
  100. 0
    0
      src/store/reducers/index.js

+ 15
- 15
package-lock.json Wyświetl plik

@@ -28269,32 +28269,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"
}
}

+ 13
- 15
src/App.js Wyświetl plik

@@ -4,25 +4,23 @@ import { Helmet } from "react-helmet-async";
import i18next from "i18next";
import history from "./store/utils/history";
import AppRoutes from "./AppRoutes";
import GlobalStyle from "./components/Styles/globalStyles";
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>
{/* <main className="l-page"> */}

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


+ 4
- 3
src/AppRoutes.js Wyświetl plik

@@ -20,7 +20,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';
@@ -43,12 +43,13 @@ const AppRoutes = () => {
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage}/>
<Route path={CREATE_OFFER_PAGE} component={CreateOffer}/>
<Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} />
<Route path={HOME_PAGE} component={HomePage} />
{/*
<PrivateRoute
exact
path={HOME_PAGE}
component={HomePage}
/>
/> */}
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>
)};

+ 7
- 0
src/assets/images/logo.svg Wyświetl plik

@@ -0,0 +1,7 @@
<svg width="129" height="53" viewBox="0 0 129 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M65.864 17.896H74.63V19.912H71.246V31H69.158V19.912H65.864V17.896ZM77.6259 23.386L77.8059 25.546L77.7339 25.24C77.9499 24.784 78.2439 24.394 78.6159 24.07C78.9999 23.734 79.3899 23.482 79.7859 23.314C80.1819 23.134 80.5059 23.044 80.7579 23.044L80.6679 25.06C80.0919 25.012 79.5879 25.12 79.1559 25.384C78.7359 25.636 78.4059 25.972 78.1659 26.392C77.9379 26.812 77.8239 27.256 77.8239 27.724V31H75.8079V23.386H77.6259ZM85.8146 31.198C85.0946 31.198 84.4406 31.048 83.8526 30.748C83.2766 30.448 82.8206 30.004 82.4846 29.416C82.1486 28.816 81.9806 28.078 81.9806 27.202C81.9806 26.338 82.1546 25.6 82.5026 24.988C82.8626 24.364 83.3306 23.884 83.9066 23.548C84.4946 23.212 85.1306 23.044 85.8146 23.044C86.5466 23.044 87.1346 23.188 87.5786 23.476C88.0346 23.764 88.3886 24.106 88.6406 24.502L88.5326 24.79L88.7306 23.386H90.5846V31H88.5686V29.146L88.7666 29.632C88.7186 29.704 88.6226 29.824 88.4786 29.992C88.3346 30.148 88.1366 30.322 87.8846 30.514C87.6326 30.706 87.3326 30.868 86.9846 31C86.6486 31.132 86.2586 31.198 85.8146 31.198ZM86.3726 29.542C86.7326 29.542 87.0566 29.476 87.3446 29.344C87.6446 29.212 87.8966 29.026 88.1006 28.786C88.3166 28.546 88.4726 28.258 88.5686 27.922V26.266C88.4726 25.954 88.3166 25.684 88.1006 25.456C87.8846 25.216 87.6206 25.03 87.3086 24.898C87.0086 24.766 86.6726 24.7 86.3006 24.7C85.8926 24.7 85.5146 24.802 85.1666 25.006C84.8186 25.21 84.5366 25.492 84.3206 25.852C84.1166 26.212 84.0146 26.626 84.0146 27.094C84.0146 27.55 84.1226 27.964 84.3386 28.336C84.5546 28.708 84.8426 29.002 85.2026 29.218C85.5626 29.434 85.9526 29.542 86.3726 29.542ZM95.1864 23.386L95.3664 24.934L95.2944 24.808C95.5824 24.256 95.9724 23.824 96.4644 23.512C96.9684 23.2 97.5504 23.044 98.2104 23.044C98.6304 23.044 99.0024 23.11 99.3264 23.242C99.6504 23.374 99.9204 23.56 100.136 23.8C100.364 24.04 100.514 24.34 100.586 24.7L100.478 24.736C100.79 24.22 101.192 23.812 101.684 23.512C102.176 23.2 102.698 23.044 103.25 23.044C103.994 23.044 104.588 23.26 105.032 23.692C105.476 24.112 105.704 24.658 105.716 25.33V31H103.718V26.122C103.706 25.75 103.628 25.444 103.484 25.204C103.34 24.952 103.07 24.814 102.674 24.79C102.242 24.79 101.87 24.928 101.558 25.204C101.246 25.468 101 25.816 100.82 26.248C100.652 26.668 100.568 27.118 100.568 27.598V31H98.5344V26.122C98.5224 25.75 98.4384 25.444 98.2824 25.204C98.1264 24.952 97.8504 24.814 97.4544 24.79C97.0344 24.79 96.6684 24.928 96.3564 25.204C96.0444 25.468 95.8044 25.816 95.6364 26.248C95.4684 26.668 95.3844 27.112 95.3844 27.58V31H93.3684V23.386H95.1864ZM113.112 31.198C112.584 31.198 112.074 31.084 111.582 30.856C111.09 30.628 110.694 30.316 110.394 29.92L110.502 29.308V34.654H108.486V23.242H110.232L110.502 25.006L110.322 24.466C110.694 24.058 111.144 23.722 111.672 23.458C112.212 23.182 112.818 23.044 113.49 23.044C114.21 23.044 114.852 23.212 115.416 23.548C115.98 23.884 116.424 24.364 116.748 24.988C117.084 25.6 117.252 26.326 117.252 27.166C117.252 28.006 117.072 28.726 116.712 29.326C116.352 29.926 115.86 30.388 115.236 30.712C114.612 31.036 113.904 31.198 113.112 31.198ZM112.716 29.596C113.16 29.596 113.568 29.494 113.94 29.29C114.324 29.086 114.63 28.804 114.858 28.444C115.086 28.072 115.2 27.658 115.2 27.202C115.2 26.722 115.092 26.302 114.876 25.942C114.672 25.57 114.39 25.282 114.03 25.078C113.682 24.862 113.292 24.754 112.86 24.754C112.464 24.754 112.11 24.82 111.798 24.952C111.486 25.084 111.222 25.276 111.006 25.528C110.79 25.768 110.622 26.056 110.502 26.392V27.922C110.586 28.246 110.73 28.534 110.934 28.786C111.138 29.038 111.396 29.236 111.708 29.38C112.02 29.524 112.356 29.596 112.716 29.596ZM122.588 31.198C121.868 31.198 121.214 31.048 120.626 30.748C120.05 30.448 119.594 30.004 119.258 29.416C118.922 28.816 118.754 28.078 118.754 27.202C118.754 26.338 118.928 25.6 119.276 24.988C119.636 24.364 120.104 23.884 120.68 23.548C121.268 23.212 121.904 23.044 122.588 23.044C123.32 23.044 123.908 23.188 124.352 23.476C124.808 23.764 125.162 24.106 125.414 24.502L125.306 24.79L125.504 23.386H127.358V31H125.342V29.146L125.54 29.632C125.492 29.704 125.396 29.824 125.252 29.992C125.108 30.148 124.91 30.322 124.658 30.514C124.406 30.706 124.106 30.868 123.758 31C123.422 31.132 123.032 31.198 122.588 31.198ZM123.146 29.542C123.506 29.542 123.83 29.476 124.118 29.344C124.418 29.212 124.67 29.026 124.874 28.786C125.09 28.546 125.246 28.258 125.342 27.922V26.266C125.246 25.954 125.09 25.684 124.874 25.456C124.658 25.216 124.394 25.03 124.082 24.898C123.782 24.766 123.446 24.7 123.074 24.7C122.666 24.7 122.288 24.802 121.94 25.006C121.592 25.21 121.31 25.492 121.094 25.852C120.89 26.212 120.788 26.626 120.788 27.094C120.788 27.55 120.896 27.964 121.112 28.336C121.328 28.708 121.616 29.002 121.976 29.218C122.336 29.434 122.726 29.542 123.146 29.542Z" fill="#5A3984"/>
<rect x="17.5078" width="24.7529" height="24.7529" rx="6.3546" transform="rotate(45 17.5078 0)" fill="#5A3984"/>
<rect x="35.0078" y="17.5032" width="24.7529" height="24.7529" rx="6.3546" transform="rotate(45 35.0078 17.5032)" fill="#FEB005"/>
<circle cx="29.5859" cy="22.7096" r="4.85659" transform="rotate(45 29.5859 22.7096)" fill="#FEB005"/>
<circle cx="22.8281" cy="29.6886" r="4.85659" transform="rotate(45 22.8281 29.6886)" fill="#5A3984"/>
</svg>

+ 7
- 0
src/assets/images/logo_vertical.svg
Plik diff jest za duży
Wyświetl plik


+ 3
- 0
src/assets/images/svg/filter.svg Wyświetl plik

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

+ 5
- 0
src/assets/images/svg/log-out.svg Wyświetl plik

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

+ 43
- 0
src/assets/images/svg/logo-horizontal.svg Wyświetl plik

@@ -0,0 +1,43 @@
<svg
width="129"
height="53"
viewBox="0 0 129 53"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M65.864 17.896H74.63V19.912H71.246V31H69.158V19.912H65.864V17.896ZM77.6259 23.386L77.8059 25.546L77.7339 25.24C77.9499 24.784 78.2439 24.394 78.6159 24.07C78.9999 23.734 79.3899 23.482 79.7859 23.314C80.1819 23.134 80.5059 23.044 80.7579 23.044L80.6679 25.06C80.0919 25.012 79.5879 25.12 79.1559 25.384C78.7359 25.636 78.4059 25.972 78.1659 26.392C77.9379 26.812 77.8239 27.256 77.8239 27.724V31H75.8079V23.386H77.6259ZM85.8146 31.198C85.0946 31.198 84.4406 31.048 83.8526 30.748C83.2766 30.448 82.8206 30.004 82.4846 29.416C82.1486 28.816 81.9806 28.078 81.9806 27.202C81.9806 26.338 82.1546 25.6 82.5026 24.988C82.8626 24.364 83.3306 23.884 83.9066 23.548C84.4946 23.212 85.1306 23.044 85.8146 23.044C86.5466 23.044 87.1346 23.188 87.5786 23.476C88.0346 23.764 88.3886 24.106 88.6406 24.502L88.5326 24.79L88.7306 23.386H90.5846V31H88.5686V29.146L88.7666 29.632C88.7186 29.704 88.6226 29.824 88.4786 29.992C88.3346 30.148 88.1366 30.322 87.8846 30.514C87.6326 30.706 87.3326 30.868 86.9846 31C86.6486 31.132 86.2586 31.198 85.8146 31.198ZM86.3726 29.542C86.7326 29.542 87.0566 29.476 87.3446 29.344C87.6446 29.212 87.8966 29.026 88.1006 28.786C88.3166 28.546 88.4726 28.258 88.5686 27.922V26.266C88.4726 25.954 88.3166 25.684 88.1006 25.456C87.8846 25.216 87.6206 25.03 87.3086 24.898C87.0086 24.766 86.6726 24.7 86.3006 24.7C85.8926 24.7 85.5146 24.802 85.1666 25.006C84.8186 25.21 84.5366 25.492 84.3206 25.852C84.1166 26.212 84.0146 26.626 84.0146 27.094C84.0146 27.55 84.1226 27.964 84.3386 28.336C84.5546 28.708 84.8426 29.002 85.2026 29.218C85.5626 29.434 85.9526 29.542 86.3726 29.542ZM95.1864 23.386L95.3664 24.934L95.2944 24.808C95.5824 24.256 95.9724 23.824 96.4644 23.512C96.9684 23.2 97.5504 23.044 98.2104 23.044C98.6304 23.044 99.0024 23.11 99.3264 23.242C99.6504 23.374 99.9204 23.56 100.136 23.8C100.364 24.04 100.514 24.34 100.586 24.7L100.478 24.736C100.79 24.22 101.192 23.812 101.684 23.512C102.176 23.2 102.698 23.044 103.25 23.044C103.994 23.044 104.588 23.26 105.032 23.692C105.476 24.112 105.704 24.658 105.716 25.33V31H103.718V26.122C103.706 25.75 103.628 25.444 103.484 25.204C103.34 24.952 103.07 24.814 102.674 24.79C102.242 24.79 101.87 24.928 101.558 25.204C101.246 25.468 101 25.816 100.82 26.248C100.652 26.668 100.568 27.118 100.568 27.598V31H98.5344V26.122C98.5224 25.75 98.4384 25.444 98.2824 25.204C98.1264 24.952 97.8504 24.814 97.4544 24.79C97.0344 24.79 96.6684 24.928 96.3564 25.204C96.0444 25.468 95.8044 25.816 95.6364 26.248C95.4684 26.668 95.3844 27.112 95.3844 27.58V31H93.3684V23.386H95.1864ZM113.112 31.198C112.584 31.198 112.074 31.084 111.582 30.856C111.09 30.628 110.694 30.316 110.394 29.92L110.502 29.308V34.654H108.486V23.242H110.232L110.502 25.006L110.322 24.466C110.694 24.058 111.144 23.722 111.672 23.458C112.212 23.182 112.818 23.044 113.49 23.044C114.21 23.044 114.852 23.212 115.416 23.548C115.98 23.884 116.424 24.364 116.748 24.988C117.084 25.6 117.252 26.326 117.252 27.166C117.252 28.006 117.072 28.726 116.712 29.326C116.352 29.926 115.86 30.388 115.236 30.712C114.612 31.036 113.904 31.198 113.112 31.198ZM112.716 29.596C113.16 29.596 113.568 29.494 113.94 29.29C114.324 29.086 114.63 28.804 114.858 28.444C115.086 28.072 115.2 27.658 115.2 27.202C115.2 26.722 115.092 26.302 114.876 25.942C114.672 25.57 114.39 25.282 114.03 25.078C113.682 24.862 113.292 24.754 112.86 24.754C112.464 24.754 112.11 24.82 111.798 24.952C111.486 25.084 111.222 25.276 111.006 25.528C110.79 25.768 110.622 26.056 110.502 26.392V27.922C110.586 28.246 110.73 28.534 110.934 28.786C111.138 29.038 111.396 29.236 111.708 29.38C112.02 29.524 112.356 29.596 112.716 29.596ZM122.588 31.198C121.868 31.198 121.214 31.048 120.626 30.748C120.05 30.448 119.594 30.004 119.258 29.416C118.922 28.816 118.754 28.078 118.754 27.202C118.754 26.338 118.928 25.6 119.276 24.988C119.636 24.364 120.104 23.884 120.68 23.548C121.268 23.212 121.904 23.044 122.588 23.044C123.32 23.044 123.908 23.188 124.352 23.476C124.808 23.764 125.162 24.106 125.414 24.502L125.306 24.79L125.504 23.386H127.358V31H125.342V29.146L125.54 29.632C125.492 29.704 125.396 29.824 125.252 29.992C125.108 30.148 124.91 30.322 124.658 30.514C124.406 30.706 124.106 30.868 123.758 31C123.422 31.132 123.032 31.198 122.588 31.198ZM123.146 29.542C123.506 29.542 123.83 29.476 124.118 29.344C124.418 29.212 124.67 29.026 124.874 28.786C125.09 28.546 125.246 28.258 125.342 27.922V26.266C125.246 25.954 125.09 25.684 124.874 25.456C124.658 25.216 124.394 25.03 124.082 24.898C123.782 24.766 123.446 24.7 123.074 24.7C122.666 24.7 122.288 24.802 121.94 25.006C121.592 25.21 121.31 25.492 121.094 25.852C120.89 26.212 120.788 26.626 120.788 27.094C120.788 27.55 120.896 27.964 121.112 28.336C121.328 28.708 121.616 29.002 121.976 29.218C122.336 29.434 122.726 29.542 123.146 29.542Z"
fill="#5A3984"
/>
<rect
x="17.5078"
width="24.7529"
height="24.7529"
rx="6.3546"
transform="rotate(45 17.5078 0)"
fill="#5A3984"
/>
<rect
x="35.0078"
y="17.5032"
width="24.7529"
height="24.7529"
rx="6.3546"
transform="rotate(45 35.0078 17.5032)"
fill="#FEB005"
/>
<circle
cx="29.5859"
cy="22.7096"
r="4.85659"
transform="rotate(45 29.5859 22.7096)"
fill="#FEB005"
/>
<circle
cx="22.8281"
cy="29.6886"
r="4.85659"
transform="rotate(45 22.8281 29.6886)"
fill="#5A3984"
/>
</svg>

+ 1
- 1
src/assets/styles/_base.scss Wyświetl plik

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

* {

+ 1
- 1
src/components/Buttons/PrimaryButton/PrimaryButton.js Wyświetl plik

@@ -11,7 +11,7 @@ export const PrimaryButton = (props) => {
style={props.containerStyle}
className={props.className}
>
<PrimaryButtonStyled {...props} sx={props.style}>
<PrimaryButtonStyled {...props} buttoncolor={props.buttoncolor} sx={props.style}>
{props.children}
</PrimaryButtonStyled>
</PrimaryButtonContainer>

+ 4
- 2
src/components/Buttons/PrimaryButton/PrimaryButton.styled.js Wyświetl plik

@@ -1,8 +1,10 @@
import { Box, Button } from "@mui/material";
import { Button } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";

export const PrimaryButtonContainer = styled(Box)``;
export const PrimaryButtonContainer = styled.div`
min-width: fit-content;
`;

export const PrimaryButtonStyled = styled(Button)`
background-color: ${(props) =>

+ 2
- 2
src/components/Cards/CreateOfferCard/CreateOffer.js Wyświetl plik

@@ -6,7 +6,7 @@ import { useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import { fetchUser } from "../../../store/actions/login/loginActions";
import { fetchLogin } from "../../../store/actions/login/loginActions";
import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../../constants/pages";
import { ReactComponent as VisibilityOn } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../../assets/images/svg/eye.svg";
@@ -67,7 +67,7 @@ const CreateOffer = ({ history }) => {
const handleSubmit = (values) => {
const { username: email, password: password } = values;
dispatch(
fetchUser({
fetchLogin({
email,
password,
handleApiResponseSuccess,

+ 24
- 4
src/components/Cards/CreateOfferCard/CreateOffer.styled.js Wyświetl plik

@@ -50,13 +50,13 @@ export const RegisterAltText = styled(Typography)`
font-size: 14px;
padding-right: 6px;
line-height: 14px;
`
`;
export const RegisterTextContainer = styled(Box)`
display: flex;
flex-direction: row;
margin-top: 36px;
justify-content: center;
`
`;
export const FieldLabel = styled(Label)`
position: relative;
bottom: -14px;
@@ -68,9 +68,29 @@ export const FieldLabel = styled(Label)`
cursor: auto;
letter-spacing: 0.2px;
}
`
`;
export const SelectText = styled(Typography)`
font-size: 16px;
font-family: "Open Sans";
font-weight: 400;
`;
export const SelectField = styled(Select)`
position: relative;
top: 15px;
margin-bottom: 18px;
`
& div {
${SelectText} {
font-weight: 600;
}
}
`;

export const SelectAltText = styled(Typography)`
font-family: "Open Sans";

font-style: italic;
white-space: pre;
font-size: 12px;
position: relative;
bottom: -1px;
`;

+ 0
- 3
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js Wyświetl plik

@@ -8,8 +8,6 @@ import {
NextButton,
TitleField,
} from "./FirstPartCreateOffer.styled";
// import { TextField } from "../../../TextFields/TextField/TextField";
// import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import * as Yup from "yup";
import selectedTheme from "../../../../themes";
import { useTranslation } from "react-i18next";
@@ -19,7 +17,6 @@ import { SelectField } from "../CreateOffer.styled";
const FirstPartCreateOffer = (props) => {
const { t } = useTranslation();
const handleSubmit = (values) => {
console.log(values);
props.handleNext(values);
};
const formik = useFormik({

+ 64
- 3
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js Wyświetl plik

@@ -1,9 +1,70 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { CreateOfferFormContainer } from "./SecondPartCreateOffer.styled";
import {
CreateOfferFormContainer,
FieldLabel,
Scroller,
// ImageListStyled,
} from "./SecondPartCreateOffer.styled";
import ImagePicker from "../../../ImagePicker/ImagePicker";
// import Select from "../../../Select/Select";
import Option from "../../../Select/Option/Option";
import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled";
import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled";
import selectedTheme from "../../../../themes";
import { conditionSelectEnum } from "../../../../enums/conditionEnum";

const SecondPartCreateOffer = () => {
return <CreateOfferFormContainer>Aaaa</CreateOfferFormContainer>;
const [images, setImages] = useState([null, null, null]); // 3 images
const setImage = (index, image) => {
setImages((prevState) => {
let newState = [...prevState];
newState[index] = image;
return [...newState];
});
};

return (
<CreateOfferFormContainer>
<Scroller>
{images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
/>
))}
</Scroller>
<FieldLabel leftText="STANJE" />
<SelectField defaultValue={conditionSelectEnum.NEW.value}>
{Object.keys(conditionSelectEnum).map((key) => {
var item = conditionSelectEnum[key];
return (
<Option value={item.value} key={item.value}>
<SelectText>{item.mainText}</SelectText>
<SelectAltText>{item.altText}</SelectAltText>
</Option>
);
})}
</SelectField>

<NextButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
>
NASTAVI
</NextButton>
</CreateOfferFormContainer>
);
};

SecondPartCreateOffer.propTypes = {

+ 49
- 3
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.styled.js Wyświetl plik

@@ -1,8 +1,54 @@
import { Box } from "@mui/material";
import { Box, ImageList } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { Label } from "../../../CheckBox/Label";
import HorizontalScroller from "../../../Scroller/HorizontalScroller";
import { ReactComponent as TrashIcon } from "../../../../assets/images/svg/trash.svg";

export const CreateOfferFormContainer = styled(Box)`
width: 335px;
width: 350px;
height: 700px;
padding-top: 20px;
`;
`;

export const ImageCard = styled.img`
width: 216px;
height: 144px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
`;
export const ImageListStyled = styled(ImageList)`
display: flex;
flex: 1;
overflow: hidden;
cursor: grab;
`;
export const FieldLabel = styled(Label)`
position: relative;
top: 12px;
& label {
font-size: 12px;
font-weight: 600;
line-height: 20px;
color: ${selectedTheme.primaryGrayText};
cursor: auto;
letter-spacing: 0.2px;
}
`;
export const Scroller = styled(HorizontalScroller)`
min-width: 640px;
position: relative;
left: calc(-320px + 50%);
margin-bottom: 36px;
`;
export const Trash = styled(TrashIcon)`
cursor: pointer;
margin: auto;
width: 22px;
height: 22px;
& path {
stroke: white;
}
`;

+ 89
- 108
src/components/Cards/FilterCard/FilterCard.js Wyświetl plik

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import {
ContentContainer,
@@ -11,158 +11,130 @@ import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcat
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as CategoryChosen } from "../../../assets/images/svg/category-chosen.svg";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";

import Link from "../../Link/Link";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import FilterCheckboxDropdown from "./FilterDropdown/Checkbox/FilterCheckboxDropdown";
import Mockupdata from "./Mockupdata";
import { useState } from "react";
import FilterRadioDropdown from "./FilterDropdown/Radio/FilterRadioDropdown";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../../../constants/pages";
import qs from "query-string";
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import useFilters from "../../../hooks/useFilters";

const FilterCard = () => {
const [appliedFilters, setAppliedFilters] = useState([]);
const [selectedCategory, setSelectedCategory] = useState(0);
const [selectedSubcategory, setSelectedSubcategory] = useState(0);
const history = useHistory();
const FilterCard = (props) => {
const { t } = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);
const filters = useFilters();

useEffect(() => {
const queryString = history.location.search.substring(1);
const queryObject = qs.parse(queryString);
if (queryObject.category) {
setSelectedCategory(
Mockupdata[1].find(
(item) => item.string === queryObject.category.toString()
).id
);
}
if (queryObject.subcategory) {
setSelectedSubcategory(
Mockupdata[1].find(
(item) => item.string === queryObject.subcategory.toString()
).id
);
if (!filters.selectedCategory || filters.selectedCategory?._id === 0) {
setIsOpened(false);
setIsDisabled(true);
} else {
setIsDisabled(false);
}
if (queryObject.city) {
let filters = [];
if (Array.isArray(queryObject.city)) {
queryObject.city.forEach((item) => {
filters.push(Mockupdata[0].find((p) => p.string === item).id);
});
} else {
filters.push(
Mockupdata[0].find((p) => p.string === queryObject.city).id
);
}
setAppliedFilters([...filters]);
}
}, []);
}, [filters.selectedCategory]);

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

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

const handleFilters = () => {
let queryObject = {};
if (selectedCategory !== 0) {
queryObject = {
category: Mockupdata[1].find(
(item) => item.id.toString() === selectedCategory.toString()
).string,
};
if (selectedSubcategory !== 0) {
queryObject = {
...queryObject,
subcategory: Mockupdata[1].find(
(item) => item.id.toString() === selectedSubcategory.toString()
).string,
};
}
}
if (appliedFilters.length > 0) {
let arrayObject = [];
appliedFilters.forEach((item) => {
arrayObject.push(
Mockupdata[0].find((p) => p.id.toString() === item.toString()).string
);
});
queryObject = { ...queryObject, city: arrayObject };
}
const queryString = qs.stringify(queryObject);
history.push({
pathname: HOME_PAGE,
search: "?" + queryString,
});
window.scrollTo({
top: 0,
behavior: "smooth",
});
filters.applyFilters();
if (props.closeResponsive) props.closeResponsive();
};
const clearFilters = () => {
setAppliedFilters([]);
setSelectedCategory(0);
setSelectedSubcategory(0);
history.push({
pathname: HOME_PAGE,
search: "",
});
window.scrollTo({
top: 0,
behavior: "smooth",
});
filters.clearFilters();
};

return (
<FilterCardContainer>
<FilterCardContainer responsiveOpen={props.responsiveOpen} responsive={props.responsive}>
<Header>
<Title>{t("filters.title")}</Title>
<Link to="#" textsize={"12px"} font={"Open Sans"} onClick={clearFilters}>
<Link
to="#"
textsize={"12px"}
font={"Open Sans"}
onClick={clearFilters}
>
{t("filters.cancel")}
</Link>
</Header>
<ContentContainer>
{/* Categories */}
<FilterRadioDropdown
data={[...Mockupdata[1]]}
data={[...filters?.categories]}
icon={
selectedCategory && selectedCategory !== 0 ? (
<CategoryChosen />
) : (
<Category />
)
filters.selectedCategory?.name ? <CategoryChosen /> : <Category />
}
title={
selectedCategory && selectedCategory !== 0
? Mockupdata[1].find(
(item) => item.id.toString() === selectedCategory.toString()
).string
filters.selectedCategory?.name
? filters.selectedCategory?.name
: t("filters.categories.title")
}
searchPlaceholder={t("filters.categories.placeholder")}
setSelected={setSelectedCategory}
selected={selectedCategory}
setSelected={handleSelectCategory}
selected={filters.selectedCategory}
firstOption={{
label: "SVE KATEGORIJE",
value: { _id: 0 },
}}
/>

{/* Subcategories */}
<FilterRadioDropdown
data={[...Mockupdata[1]]}
data={filters.subcategories ? [...filters.subcategories] : []}
icon={<Subcategory />}
title={t("filters.subcategories.title")}
title={
filters.selectedSubcategory?.name
? filters.selectedSubcategory?.name
: t("filters.subcategories.title")
}
searchPlaceholder={t("filters.subcategories.placeholder")}
setSelected={setSelectedSubcategory}
selected={selectedSubcategory}
setSelected={filters.setSelectedSubcategory}
selected={filters.selectedSubcategory}
open={isOpened}
disabled={isDisabled}
handleOpen={handleOpen}
firstOption={{
label: "SVE PODKATEGORIJE",
value: { _id: 0 },
}}
/>

{/* Locations */}
<FilterCheckboxDropdown
searchPlaceholder={t("filters.location.placeholder")}
data={[...Mockupdata[0]]}
filters={appliedFilters}
data={[...filters.locations]}
filters={[...filters.selectedLocations]}
icon={<Location />}
title={t("filters.location.title")}
setItemsSelected={setAppliedFilters}
setItemsSelected={filters.setSelectedLocations}
/>
</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
@@ -184,6 +156,15 @@ const FilterCard = () => {

FilterCard.propTypes = {
children: PropTypes.node,
filters: PropTypes.any,
responsive: PropTypes.bool,
responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func,
};

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

export default FilterCard;

+ 25
- 5
src/components/Cards/FilterCard/FilterCard.styled.js Wyświetl plik

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

+ 31
- 28
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js Wyświetl plik

@@ -24,34 +24,27 @@ const FilterCheckboxDropdown = (props) => {

useEffect(() => {
setDataToShow([...data]);
}, []);
useEffect(() => {
console.log("props.filters: ", props.filters);
}, [props.filters])

}, [data]);
useEffect(() => {
if (toSearch.length > 0) {
setDataToShow(
data.filter((item) =>
item.string.toLowerCase().includes(toSearch.toLowerCase())
item.city.toLowerCase().includes(toSearch.toLowerCase())
)
);
} else {
setDataToShow([...data]);
}
}, [toSearch]);

const handleChange = (item) => {
if (props.oneValueAllowed) {
props.setItemsSelected[item.id];
props.setItemsSelected([item]);
} else {
if (props.filters.includes(item.id)) {
props.setItemsSelected((itemsSelected) => [
...itemsSelected.filter((p) => p !== item.id),
]);
if (props.filters.find(itemInList => itemInList.city.toString() === item.city.toString())) {
props.setItemsSelected([...props.filters.filter((p) => p.city.toString() !== item.city.toString())]);
} else {
props.setItemsSelected((itemsSelected) => [...itemsSelected, item.id]);
props.setItemsSelected([...props.filters, item]);
}
}
};
@@ -80,14 +73,19 @@ const FilterCheckboxDropdown = (props) => {
fullWidth
setIsOpened={setIsOpened}
toggleIconStyles={{
backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor,
backgroundColor: isOpened
? "white"
: selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
<React.Fragment>
<SelectedItemsContainer>
{props.filters.map((item) => (
<SelectedItem key={item} onClick={() => handleDelete(item)}>
{data.find((p) => p.id === item).string}
<SelectedItem key={item.city} onClick={() => handleDelete(item)}>
{
data.find((p) => p.city.toString() === item.city.toString())
?.city
}
<Close style={{ position: "relative", top: "3px" }} />
</SelectedItem>
))}
@@ -117,18 +115,23 @@ const FilterCheckboxDropdown = (props) => {
</React.Fragment>
}
>
{dataToShow.map((item) => (
<DropdownItem key={item.id}>
<CheckBox
leftText={item.string}
rightText={item.numberOfProducts}
value={item.id}
checked={props.filters.includes(item.id)}
onChange={() => handleChange(item)}
fullWidth
/>
</DropdownItem>
))}
{dataToShow.map((item) => {
return (
<DropdownItem key={item.city}>
<CheckBox
leftText={item.city}
rightText={item.offerCount}
value={item}
checked={props.filters.find(
(itemInList) =>
itemInList.city.toString() === item.city.toString()
) ? true : false}
onChange={() => handleChange(item)}
fullWidth
/>
</DropdownItem>
);
})}
</DropdownList>
);
};

+ 54
- 20
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js Wyświetl plik

@@ -16,16 +16,17 @@ const FilterRadioDropdown = (props) => {
const [dataToShow, setDataToShow] = useState([]);
const [isOpened, setIsOpened] = useState(false);
const { data } = props;

useEffect(() => {
setDataToShow([...data]);
}, []);
}, [data]);

useEffect(() => {
if (toSearch.length > 0) {
setDataToShow(
data.filter((item) =>
item.string.toLowerCase().includes(toSearch.toLowerCase())
item.name
? item.name.toLowerCase().includes(toSearch.toLowerCase())
: item.toLowerCase().includes(toSearch.toLowerCase())
)
);
} else {
@@ -36,27 +37,37 @@ const FilterRadioDropdown = (props) => {
const handleClear = () => {
setToSearch("");
};
const handleChange = (value) => {
props.setSelected(value);
const handleOpen = () => {
setIsOpened((prevState) => !prevState);
if (props.handleOpen) props.handleOpen();
};

return (
<DropdownList
title={props.title}
textcolor={
props.selected !== 0
? selectedTheme.primaryPurple
: selectedTheme.primaryText
!props.selected || props.selected?._id === 0
? selectedTheme.primaryText
: selectedTheme.primaryPurple
}
dropdownIcon={props.icon}
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
setIsOpened={setIsOpened}
open={props.open}
disabled={props.disabled}
setIsOpened={handleOpen}
toggleIconStyles={{
backgroundColor: isOpened ? "white" : selectedTheme.primaryIconBackgroundColor,
backgroundColor: (
props.open !== undefined || props.open !== null
? props.open
: isOpened
)
? "white"
: selectedTheme.primaryIconBackgroundColor,
}}
headerOptions={
// SearchBar
<React.Fragment>
<TextField
placeholder={props.searchPlaceholder}
@@ -82,19 +93,38 @@ const FilterRadioDropdown = (props) => {
</React.Fragment>
}
>
<RadioGroup onChange={(event) => handleChange(event.target.value)}>
{dataToShow.map((item) => (
<DropdownItem key={item.id}>
<RadioGroup>
{props.firstOption && (
<DropdownItem>
<RadioButton
value={item.id}
label={item.string}
number={item.numberOfProducts}
value={props.firstOption.value}
label={props.firstOption.label}
// number={item.numberOfProducts}
fullWidth
checked={props.selected.toString() === item.id.toString()}
onChange={handleChange}
checked={!props.selected || props.selected._id === 0}
onChange={props.setSelected}
/>
</DropdownItem>
))}
)}
{dataToShow.map((item) => {
return (
<DropdownItem
key={item.name}
onClick={() => props.setSelected(item)}
>
<RadioButton
value={item}
label={item.name ? item.name : item}
number={item.offerCount}
fullWidth
checked={
JSON.stringify(props.selected) === JSON.stringify(item)
}
onChange={props.setSelected}
/>
</DropdownItem>
);
})}
</RadioGroup>
</DropdownList>
);
@@ -109,7 +139,11 @@ FilterRadioDropdown.propTypes = {
fullWidth: PropTypes.bool,
searchPlaceholder: PropTypes.string,
setSelected: PropTypes.func,
selected: PropTypes.number,
selected: PropTypes.any,
firstOption: PropTypes.any,
disabled: PropTypes.bool,
open: PropTypes.bool,
handleOpen: PropTypes.func,
};
FilterRadioDropdown.defaultProps = {
oneValueAllowed: false,

+ 22
- 15
src/components/Cards/OfferCard/OfferCard.js Wyświetl plik

@@ -4,6 +4,7 @@ import {
CheckButton,
DetailIcon,
DetailText,
EyeIcon,
Line,
MessageIcon,
OfferAuthor,
@@ -14,16 +15,16 @@ import {
OfferDescriptionText,
OfferDescriptionTitle,
OfferDetails,
OfferFlexContainer,
OfferImage,
OfferImageContainer,
OfferInfo,
OfferLocation,
OfferPackage,
OfferTitle,
OfferTitleAboveImage,
OfferViews,
} from "./OfferCard.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.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";
import { useHistory } from "react-router-dom";
@@ -40,8 +41,16 @@ const OfferCard = (props) => {
history.push(`/proizvodi/${itemId}`)
}
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>
@@ -55,26 +64,22 @@ const OfferCard = (props) => {
</DetailIcon>
<DetailText>{props.offer.category.name}</DetailText>
</OfferCategory>
<OfferPackage>
<DetailIcon color="black" component="span" size="16px">
<Quantity width={"12px"} height={"12px"} />
</DetailIcon>
<DetailText>{props.offer.quantity} pakovanja</DetailText>
</OfferPackage>
<OfferViews>
<DetailIcon color="black" component="span" size="16px">
<Eye width={"12px"} height={"11px"} />
<EyeIcon />
</DetailIcon>
<DetailText>{props.offer.views.viewers.length} pregleda</DetailText>
<DetailText>{props.offer.views.viewers.length}</DetailText>
</OfferViews>
</OfferDetails>
</OfferInfo>
{!props.halfwidth ? (
<React.Fragment>
<Line/>
<Line />
<OfferDescription>
<OfferDescriptionTitle>Opis:</OfferDescriptionTitle>
<OfferDescriptionText>{props.offer.description}</OfferDescriptionText>
<OfferDescriptionText>
{props.offer.description}
</OfferDescriptionText>
</OfferDescription>

<CheckButton
@@ -104,7 +109,9 @@ const OfferCard = (props) => {
{props.quantity}
{props.package}
{props.numberOfViews} */}
</OfferFlexContainer>
</OfferCardContainer>
</React.Fragment>
);
};


+ 92
- 12
src/components/Cards/OfferCard/OfferCard.styled.js Wyświetl plik

@@ -4,25 +4,51 @@ 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 OfferImage = styled.img``;
export const OfferInfo = styled(Box)`
display: flex;
flex: 2;
@@ -36,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;
@@ -46,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;
`;
@@ -60,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";
@@ -81,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";
@@ -163,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
- 1
src/components/CheckBox/CheckBox.js Wyświetl plik

@@ -55,7 +55,7 @@ CheckBox.propTypes = {
rightText: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
maxWidth: PropTypes.string,
checked: PropTypes.bool,
value: PropTypes.number,
value: PropTypes.any,
onChange: PropTypes.func,
containerStyle: PropTypes.any,
checkBoxStyle: PropTypes.any,

+ 1
- 1
src/components/CheckBox/Label.js Wyświetl plik

@@ -11,7 +11,7 @@ export const Label = (props) => {
className={props.className}
>
<LeftLabel style={props.leftTextStyle}>{props.leftText}</LeftLabel>
{props.rightText && <RightLabel>{props.rightText}</RightLabel>}
{props.rightText !== null ? <RightLabel>{props.rightText}</RightLabel> : <></>}
</LabelContainer>
);
};

+ 28
- 8
src/components/Dropdown/DropdownList/DropdownList.js Wyświetl plik

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
DropdownListContainer,
DropdownHeader,
@@ -14,40 +14,58 @@ import PropTypes from "prop-types";

const DropdownList = (props) => {
const [listShown, setListShown] = useState(props.defaultOpen);
useEffect(() => {
if (props.open !== null || props.open !== undefined) {
setListShown(props.open);
}
}, [props.open]);
const handleShow = () => {
if (props.setIsOpened) {
props.setIsOpened(!listShown);
}
setListShown((prevState) => !prevState);
if (!props.disabled) {
setListShown((prevState) => !prevState);
}
if (!(props.open !== null || props.open !== undefined)) {
setListShown(prevState => !prevState)
}
};
return (
<DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}>
<DropdownHeader>
{props.dropdownIcon && (
<DropdownIcon onClick={() => handleShow()}>
<DropdownIcon
onClick={!props.disabled ? () => handleShow() : () => {}}
disabled={props.disabled}
>
{props.dropdownIcon}
</DropdownIcon>
)}
<DropdownTitle onClick={() => handleShow()} textcolor={props.textcolor}>
<DropdownTitle
onClick={!props.disabled ? () => handleShow() : () => {}}
textcolor={props.textcolor}
disabled={props.disabled}
>
{props.title}
</DropdownTitle>
{listShown ? (
{(props.open !== null && props.open !== undefined ? props.open : listShown) ? (
<ToggleIconOpened
style={props.toggleIconStyles}
onClick={() => handleShow()}
onClick={!props.disabled ? () => handleShow() : () => {}}
>
{props.toggleIconOpened}
</ToggleIconOpened>
) : (
<ToggleIconClosed
style={props.toggleIconStyles}
onClick={() => handleShow()}
onClick={!props.disabled ? () => handleShow() : () => {}}
disabled={props.disabled}
>
{props.toggleIconClosed}
</ToggleIconClosed>
)}
</DropdownHeader>
<ToggleContainer shouldShow={listShown}>
<ToggleContainer shouldShow={props.open !== null && props.open !== undefined ? props.open : listShown}>
<DropdownOptions>{props.headerOptions}</DropdownOptions>
<ListContainer>{props.children}</ListContainer>
</ToggleContainer>
@@ -69,6 +87,8 @@ DropdownList.propTypes = {
headerOptions: PropTypes.node,
textcolor: PropTypes.string,
setIsOpened: PropTypes.func,
open: PropTypes.bool,
disabled: PropTypes.bool,
};

DropdownList.defaultProps = {

+ 32
- 8
src/components/Dropdown/DropdownList/DropdownList.styled.js Wyświetl plik

@@ -20,32 +20,57 @@ export const DropdownTitle = styled(Typography)`
padding-top: 5px;
padding-right: 0.9rem;
font-family: "Open Sans";
color: ${props => props.textcolor ? props.textcolor : selectedTheme.primaryText};
color: ${(props) =>
props.disabled
? selectedTheme.iconStrokeDisabledColor
: props.textcolor
? props.textcolor
: selectedTheme.primaryText};
`;

export const ToggleIconOpened = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
${(props) =>
props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
}
`;

export const ToggleIconClosed = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.primaryPurple};
&:hover button {
${(props) =>
props.disabled &&
`background-color: ${selectedTheme.primaryIconBackgroundColor}`}
}
& span {
${props => props.backgroundColor ? `background-color: ${props.backgroundColor}` : ``}
${(props) =>
props.backgroundColor || !props.disabled
? `background-color: ${props.backgroundColor}`
: ``}
}
& svg path {
${(props) =>
props.disabled &&
`
stroke: ${selectedTheme.iconStrokeDisabledColor};
`}
}
`;

export const DropdownIcon = styled(IconButton)`
font-size: 22px !important;

& span {
& svg {
font-size: 22px;
& svg {
font-size: 22px;
& path {
${(props) =>
props.disabled &&
`
stroke: ${selectedTheme.iconStrokeDisabledColor};
`}
}
}
`;
@@ -59,8 +84,7 @@ export const DropdownHeader = styled(Box)`
display: flex;
flex-direction: row;
`;
export const DropdownOptions = styled(Box)`
`;
export const DropdownOptions = styled(Box)``;
export const ToggleContainer = styled(Box)`
display: ${(props) => (props.shouldShow ? "block" : "none")};
`;

+ 458
- 0
src/components/Header/Header.js Wyświetl plik

@@ -0,0 +1,458 @@
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,
Toolbar,
useMediaQuery,
Typography,
} from "@mui/material";
import { useTheme } from "@mui/system";
import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
import MailIcon from "@mui/icons-material/EmailOutlined";
import Autorenew from "@mui/icons-material/Autorenew";
import AccountCircle from "@mui/icons-material/PersonOutlineOutlined";
import Drawer from "../MUI/DrawerComponent";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import PopoverComponent from "../Popovers/PopoverComponent";
import { MyPosts } from "../Popovers/MyPosts/MyPosts";
import { MyMessages } from "../Popovers/MyMessages/MyMessages";
import { MyProfile } from "../Popovers/MyProfile/MyProfile";
import { ReactComponent as LogoHorizontal } from "../../assets/images/svg/logo-horizontal.svg";
import selectedTheme from "../../themes";
import { useTranslation } from "react-i18next";
import { IconButton } from "../Buttons/IconButton/IconButton";
import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useSearch } from "../../hooks/useSearch";
import { selectProfileName } from "../../store/selectors/profileSelectors";
import { 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(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,
]);

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

const [msgPopoverOpen, setMsgPopoverOpen] = useState(false);
const [msgAnchorEl, setMsgAnchorEl] = useState(null);

const [userPopoverOpen, setUserPopoverOpen] = useState(false);
const [userAnchorEl, setUserAnchorEl] = useState(null);

const [shouldShow, setShouldShow] = useState(true);

useEffect(() => {
let shouldShowHeader = true;
if (
location.pathname === "/login" ||
location.pathname === "/register" ||
location.pathname === "/forgot-password" ||
location.pathname === "/reset-password" ||
location.pathname === "/"
) {
shouldShowHeader = false;
}
if (location.pathname === "/" && user?.length === 0) {
shouldShowHeader = false;
}
setShouldShow(shouldShowHeader);
}, [location.pathname, user, routeMatch]);

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};
const handleNavigateLogin = () => {
setShouldShow(false);
history.push(LOGIN_PAGE);
};
const handleNavigateRegister = () => {
setShouldShow(false);
history.push(REGISTER_PAGE);
};
const drawerContent = useMemo(
() => (
<DrawerContainer>
{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 (
<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}
/>
{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>
) : (
<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>
)}
</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>
),
}}
placeholder={t("header.searchOffers")}
italicPlaceholder
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
/>
<FilterCard
responsive={true}
responsiveOpen={openFilters}
closeResponsive={toggleFilters}
/>
</HeaderContainer>
);
};

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

export default Header;

+ 216
- 0
src/components/Header/Header.styled.js Wyświetl plik

@@ -0,0 +1,216 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import { TextField } from "../TextFields/TextField/TextField";
import { ReactComponent as Search } from "../../assets/images/svg/magnifying-glass.svg";
import { ReactComponent as Filter } from "../../assets/images/svg/filter.svg";
import selectedTheme from "../../themes";
import { Icon } from "../Icon/Icon";
import IconWithNumber from "../Icon/IconWithNumber/IconWithNumber";

export const SearchInput = styled(TextField)`
background-color: #f4f4f4;
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: 36%;
margin-left: 5%;
margin-right: 10px;
}
@media (max-width: 550px) {
display: none;
width: 0;
}
`;
export const DrawerContainer = styled(Box)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 25px;
& div {
flex-direction: column;
}
`;
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;`}
}
`;
export const LogoContainer = styled(Box)`
display: flex;
justify-content: center;
align-items: center;
`;
export const ToolsButtonsContainer = styled(Box)`
display: flex;
flex: 4;
justify-content: space-between;
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: 900px) {
flex: 0.35;
min-width: 0px;
width: 60px;
justify-content: right;
}
`;
export const ToggleDrawerButton = styled(Box)`
max-width: 40px;
`;
export const AddOfferButton = styled(PrimaryButton)`
height: 49px;
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;
flex-direction: row;
cursor: pointer;
`;
export const UserName = styled(Typography)`
color: ${selectedTheme.primaryPurple};
padding-top: 5px;
padding-right: 10px;
font-family: "Open Sans";
font-weight: 600;
white-space: nowrap;
`;
export const RegisterButton = styled(PrimaryButton)`
height: 49px;
width: 180px;
font-weight: 600;
@media (max-width: 550px) {
margin-bottom: 20px;
}
`;
export const LoginButton = styled(PrimaryButton)`
height: 49px;
width: 180px;
font-weight: 600;
margin-right: 10px;
`;
export const AuthButtonsContainer = styled(Box)`
display: flex;
justify-content: flex-start;
flex: 1;
min-width: ${(props) => (props.mobile ? "40px" : "200px")};
max-width: 600px;
align-items: flex-start;
flex-wrap: nowrap;
margin-left: 40px;
& div {
margin-left: 20px;
}
@media (max-width: 1300px) {
margin-left: 0;
}
@media (max-width: 1200px) {
min-width: 400px;
}
@media (max-width: 900px) {
min-width: 0px;
width: 0px;
justify-content: right;
}
`;

export const AuthButtonsDrawerContainer = styled(Box)`
position: relative;
left: 10px;
height: 200px;
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-around;
`;
export const SearchInputMobile = styled(SearchInput)`
@media (max-width: 550px) {
display: block;
position: relative;
width: 80%;
top: 70px;
height: 46px;
left: -50px;
font-family: "Open Sans";
& div {
background-color: white;
height: 40px;
overflow: visible;
& input {
font-size: 14px !important;
}
}
}
@media (min-width: 551px) {
display: none;
width: 0;
}
`;
export const FilterContainer = styled(IconWithNumber)`
position: relative;
top: 8px;
left: 95px;
cursor: pointer;
background-color: ${selectedTheme.offerBackgroundColor} !important;
& div {
width: 16px;
height: 16px;
background-color: ${selectedTheme.primaryPurple};
position: absolute;
top: -5px;
right: -5px;
line-height: 15px;
text-align: center;
padding-right: 2px;
}
`;
export const FilterIcon = styled(Filter)`
background-color: ${selectedTheme.offerBackgroundColor};
`;
export const HeaderContainer = styled(Box)``;

+ 2
- 1
src/components/Icon/IconWithNumber/IconWithNumber.js Wyświetl plik

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

+ 57
- 57
src/components/MUI/Examples/ModalsExample.js Wyświetl plik

@@ -1,63 +1,63 @@
import React, { useState } from 'react';
import { Button, Divider, Paper, Typography } from '@mui/material';
import DialogComponent from '../DialogComponent';
import DrawerComponent from '../DrawerComponent';
import PopoverComponent from '../PopoverComponent';
import React from 'react';
// import { Button, Divider, Paper, Typography } from '@mui/material';
// import DialogComponent from '../DialogComponent';
// import DrawerComponent from '../DrawerComponent';
// import PopoverComponent from '../PopoverComponent';

const Modals = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
// const [dialogOpen, setDialogOpen] = useState(false);
// const [drawerOpen, setDrawerOpen] = useState(false);
// const [popoverOpen, setPopoverOpen] = useState(false);
// const [anchorEl, setAnchorEl] = useState(null);

return (
<Paper
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
}}
elevation={5}
>
<Typography variant="h4" gutterBottom align="center">
Modals Example
</Typography>
<Divider />
<Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
<Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
<Button
onClick={(e) => {
setPopoverOpen(true);
setAnchorEl(e.currentTarget);
}}
>
Open Popover
</Button>
<DialogComponent
title="Dialog Title"
content={<Typography>Dialog Content</Typography>}
open={dialogOpen}
onClose={() => setDialogOpen(false)}
maxWidth="md"
fullWidth
responsive
/>
<DrawerComponent
anchor="left"
content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>}
open={drawerOpen}
toggleOpen={() => setDrawerOpen(!drawerOpen)}
/>
<PopoverComponent
anchorEl={anchorEl}
open={popoverOpen}
onClose={() => {
setPopoverOpen(false);
setAnchorEl(null);
}}
content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
/>
</Paper>
return (<></>
// <Paper
// sx={{
// p: 2,
// display: 'flex',
// flexDirection: 'column',
// }}
// elevation={5}
// >
// <Typography variant="h4" gutterBottom align="center">
// Modals Example
// </Typography>
// <Divider />
// <Button onClick={() => setDialogOpen(true)}>Open Dialog</Button>
// <Button onClick={() => setDrawerOpen(true)}>Open Drawer</Button>
// <Button
// onClick={(e) => {
// setPopoverOpen(true);
// setAnchorEl(e.currentTarget);
// }}
// >
// Open Popover
// </Button>
// <DialogComponent
// title="Dialog Title"
// content={<Typography>Dialog Content</Typography>}
// open={dialogOpen}
// onClose={() => setDialogOpen(false)}
// maxWidth="md"
// fullWidth
// responsive
// />
// <DrawerComponent
// anchor="left"
// content={<Typography sx={{ p: 2 }}>Drawer Content</Typography>}
// open={drawerOpen}
// toggleOpen={() => setDrawerOpen(!drawerOpen)}
// />
// <PopoverComponent
// anchorEl={anchorEl}
// open={popoverOpen}
// onClose={() => {
// setPopoverOpen(false);
// setAnchorEl(null);
// }}
// content={<Typography sx={{ p: 2 }}>Popover Content</Typography>}
// />
// </Paper>
);
};


+ 0
- 160
src/components/MUI/NavbarComponent.js Wyświetl plik

@@ -1,160 +0,0 @@
import React, { useState, useMemo, useContext } from 'react';
import {
AppBar,
Badge,
Box,
IconButton,
Toolbar,
Typography,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
useMediaQuery,
} from '@mui/material';
import { useTheme } from '@mui/system';
import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined';
import ShoppingBasketIcon from '@mui/icons-material/ShoppingBasket';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';
import MenuList from './MenuListComponent';
import Drawer from './DrawerComponent';
import { ColorModeContext } from '../../context/ColorModeContext';

const NavbarComponent = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down('sm'));
const toggleColorMode = useContext(ColorModeContext);

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

const drawerContent = useMemo(
() => (
<List>
<ListItemButton divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 1</ListItemText>
</ListItemIcon>
</ListItemButton>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemIcon>
<ListItemText>Link 2</ListItemText>
</ListItemIcon>
</ListItem>
<ListItem divider onClick={handleToggleDrawer}>
<ListItemText>Link 3</ListItemText>
</ListItem>
<ListItem divider>
<IconButton onClick={toggleColorMode}>
<ListItemText>Toggle {theme.palette.mode} mode</ListItemText>
{theme.palette.mode === 'dark' ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</ListItem>
</List>
),
[handleToggleDrawer]
);

return (
<AppBar
elevation={2}
sx={{ backgroundColor: 'background.default', position: 'relative' }}
>
<Toolbar>
<Box
component="div"
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
{matches ? (
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={drawerContent}
/>
) : (
<Box sx={{ display: 'flex' }}>
<Typography
variant="h6"
sx={{
marginRight: 3,
cursor: 'pointer',
color: 'text.primary',
}}
>
Link 1
</Typography>
<Typography
variant="body1"
sx={{
marginRight: 3,
cursor: 'pointer',
color: 'text.primary',
}}
>
Link 2
</Typography>
<Typography
variant="subtitle1"
sx={{
marginRight: 3,
cursor: 'pointer',
color: 'text.primary',
}}
>
Link 3
</Typography>
</Box>
)}
<Box>
<MenuList />
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{matches ? (
<Box>
<IconButton onClick={handleToggleDrawer}>
<MenuOutlinedIcon />
</IconButton>
</Box>
) : (
<Box>
<IconButton>
<Badge badgeContent={3} color="primary">
<ShoppingBasketIcon color="action" />
</Badge>
</IconButton>
<IconButton sx={{ ml: 1 }} onClick={toggleColorMode}>
{theme.palette.mode === 'dark' ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
</Box>
)}
</Box>
</Box>
</Toolbar>
</AppBar>
);
};

export default NavbarComponent;

+ 0
- 35
src/components/MUI/PopoverComponent.js Wyświetl plik

@@ -1,35 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Box, Popover } from '@mui/material';

const PopoverComponent = ({ open, anchorEl, onClose, content }) => {
const handleClose = () => {
onClose();
};

return (
<Box component="div">
<Popover
sx={{ p: 5 }}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
{content}
</Popover>
</Box>
);
};

PopoverComponent.propTypes = {
anchorEl: PropTypes.object,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
content: PropTypes.any,
};

export default PopoverComponent;

+ 74
- 72
src/components/MarketPlace/Header/Header.js Wyświetl plik

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import {
HeaderAltLocation,
HeaderButton,
HeaderButtons,
HeaderContainer,
@@ -8,16 +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 { useSelector } from "react-redux";
import { selectFilters } from "../../../store/selectors/filtersSelectors";
import Mockupdata from "../../Cards/FilterCard/Mockupdata";
import Option from "../../Select/Option/Option";
import { useHistory, useLocation, useRouteMatch } from "react-router-dom";
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}>
@@ -25,68 +27,69 @@ const DownArrow = (props) => (
</IconStyled>
);

const MockupdataForSelect = [
{
id: 0,
string: "Sortiraj po",
},
{
id: 1,
string: "Najpopularnije",
},
{
id: 2,
string: "Najnovije",
},
{
id: 3,
string: "Najstarije",
},
];

const Header = (props) => {
const [categoryString, setCategoryString] = useState("");
const [filtersString, setFiltersString] = useState("");
const { category, cities } = useSelector(selectFilters);
const history = useHistory();
const location = useLocation();
const routeMatch = useRouteMatch();
const filters = useFilters();
const sorting = useSorting();
const { t } = useTranslation();
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [headerString, setHeaderString] = useState("SVE KATEGORIJE");

useEffect(() => {
let categorystring = "";
if (category) {
categorystring = Mockupdata[1].find(
(item) => item.id === category
).string;
} else {
categorystring = "Sve kategorije";
}
let filtersstring = " | ";
if (cities) {
cities.forEach((item) => {
filtersstring = filtersstring.concat(
Mockupdata[0].find((p) => p.id === item).string + ", "
);
});
filtersstring = filtersstring.substring(0, filtersstring.length - 2);
setSortOption(sorting.selectedSortOption);
}, [sorting.selectedSortOption]);

useEffect(() => {
if (filters.isApplied) {
let headerStringLocal = "Sve kategorije";
if (filters.selectedCategory?.name) {
headerStringLocal = filters.selectedCategory.name;
if (filters.selectedSubcategory?.name) {
headerStringLocal += ` | ${filters.selectedSubcategory.name}`;
}
}
if (filters.selectedLocations && filters.selectedLocations?.length > 0) {
headerStringLocal += " | ";
filters.selectedLocations.forEach((location, index) => {
if (index + 1 === filters.selectedLocations.length) {
headerStringLocal += location.city;
} else {
headerStringLocal += location.city + ", ";
}
});
}
setHeaderString(headerStringLocal);
}
console.log("categorysstring: ", categorystring);
console.log(filtersstring);
setCategoryString(categorystring);
setFiltersString(filtersstring);
}, [category, cities]);
}, [
filters.selectedCategory,
filters.selectedLocations,
filters.selectedSubcategory,
filters.isApplied,
]);

const handleChangeSelect = (value) => {
let chosenOption = MockupdataForSelect.find(item => item.id === value);
console.log(location);
console.log(history);
console.log(chosenOption);
console.log(routeMatch)
}
const handleChangeSelect = (event) => {
let chosenOption;
for (const sortOption in sortEnum) {
if (sortEnum[sortOption].value === event.target.value) {
chosenOption = sortEnum[sortOption];
sorting.setSelectedSortOption(chosenOption);
}
}
};

return (
<HeaderContainer>
<HeaderLocation>{categoryString + filtersString} </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
@@ -111,21 +114,20 @@ const Header = (props) => {
</HeaderButton>
</HeaderButtons>
<HeaderSelect
defaultValue={0}
value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value}
IconComponent={DownArrow}
width="209px"
height="34px"
onChange={handleChangeSelect}
>
{MockupdataForSelect.map((item) => (
<Option
value={item.id}
key={item.id}
style={{ display: item.id === 0 ? "none" : "flex" }}
>
{item.string}
</Option>
))}
{Object.keys(sortEnum).map((property) => {
return (
<SelectOption
value={sortEnum[property].value}
key={sortEnum[property].value}
>
{sortEnum[property].mainText}
</SelectOption>
);
})}
</HeaderSelect>
</HeaderOptions>
</HeaderContainer>
@@ -136,7 +138,7 @@ Header.propTypes = {
children: PropTypes.node,
setIsGrid: PropTypes.func,
isGrid: PropTypes.bool,
filters: PropTypes.array,
filters: PropTypes.any,
category: PropTypes.string,
};
Header.defaultProps = {

+ 82
- 45
src/components/MarketPlace/Header/Header.styled.js Wyświetl plik

@@ -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 {
padding-left: 8px;
}
`
width: 210px;
height: 35px;
font-family: "Open Sans";
margin-top: 3px;
font-weight: 400;
position: relative;
left: -5px;
& div:first-child {
padding-left: 8px;
}

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

+ 2
- 2
src/components/MarketPlace/MarketPlace.styled.js Wyświetl plik

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

+ 112
- 15
src/components/MarketPlace/Offers/Offers.js Wyświetl plik

@@ -1,30 +1,127 @@
import React, { useEffect } from "react";
import React, { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import { fetchOffers } from "../../../store/actions/offers/offersActions";
import { useDispatch, useSelector } from "react-redux";
import { selectOffers } from "../../../store/selectors/offersSelectors";
// import { fetchOffers } from "../../../store/actions/offers/offersActions";
// import { useDispatch, useSelector } from "react-redux";
// import { selectOffers } from "../../../store/selectors/offersSelectors";
import {
selectOffers,
selectPinnedOffers,
selectTotalOffers,
} from "../../../store/selectors/offersSelectors";
// 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) => {

// Market place nije zavrsen
// Koriste se Mockup podaci
const dispatch = useDispatch();
// 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 queryStringHook = useQueryString();

// const queryString = history.location.search.substring(1);
// const queryObject = qs.parse(queryString);
useEffect(() => {
let queryObject = queryStringHook.getQueryObject();
if (queryObject.page && queryObject.page !== 1) {
setPage(parseInt(queryObject.page));
}
}, [history.location.search]);

useEffect(() => {
dispatch(fetchOffers());
}, [])
// 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 (page !== 1) {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString()) {
queryStringHook.appendToQueryString("page", page);
}
} else {
queryStringHook.appendToQueryString("page", page);
}
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>
{offers.map((item) => {
return <OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />;
})}
<OffersContainer ref={offersRef}>
<>
{pinnedOffers != undefined &&
pinnedOffers.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />
);
})}
</>
{offers != undefined &&
offers.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />
);
})}
<Paging
totalElements={total}
elementsPerPage={10}
current={page}
changePage={handleDifferentPage}
/>
</OffersContainer>
);
};

+ 3
- 0
src/components/MarketPlace/Offers/Offers.styled.js Wyświetl plik

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

+ 76
- 0
src/components/Paging/Paging.js Wyświetl plik

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

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

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

export default Paging;

+ 137
- 0
src/components/Paging/Paging.styled.js Wyświetl plik

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

+ 82
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.js Wyświetl plik

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

const HeaderPopover = (props) => {
return (
<HeaderPopoverContainer>
<PopoverTitle p={2}>{props.title}</PopoverTitle>
<PopoverList>
{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} />
)}
</PopoverListItemAvatarContainer>
<PopoverListItemTextContainer
primary={item.title}
secondary={item.text}
></PopoverListItemTextContainer>
</PopoverListItem>
)) : (
<PopoverNoItemsText>No items at the moment...</PopoverNoItemsText>
)}
</PopoverList>
<PopoverButtonsContainer>
<PopoverButton
sx={{
mr: 2,
mb: 2,
}}
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>
);
};

HeaderPopover.propTypes = {
title: PropTypes.string,
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;

+ 70
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.styled.js Wyświetl plik

@@ -0,0 +1,70 @@
import { Avatar, Box, Button, List, ListItem, ListItemAvatar, ListItemText, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ProfileAvatar } from "../MyProfile/MyProfile.styled";


export const HeaderPopoverContainer = styled(Box)`
`
export const PopoverTitle = styled(Typography)`
background-color: ${selectedTheme.primaryPurple};
color: white;
width: 100%;
min-width: 270px;
font-family: "Open Sans";
`
export const PopoverList = styled(List)`
width: 100%;
max-width: 360px;
background-color: "white";
`
export const PopoverListItem = styled(ListItem)`
align-items: flex-start;
`
export const PopoverListItemAvatar = styled(Avatar)`
position: relative;
top: 4px;
`
export const PopoverListItemProfileAvatar = styled(ProfileAvatar)`
`
export const PopoverListItemAvatarContainer = styled(ListItemAvatar)`
`
export const PopoverButton = styled(Button)`
text-decoration: underline;
color: ${selectedTheme.primaryPurple};
font-weight: 500;
text-align: right;
height: 20px;
`
export const PopoverListItemTextContainer = styled(ListItemText)`
& span {
font-weight: 700;
color: ${selectedTheme.primaryPurple};
}
& p {
font-size: 0.81rem;
& svg {
position: relative;
top: 2px;
}
}
`
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";
`

+ 42
- 0
src/components/Popovers/MyMessages/MyMessages.js Wyświetl plik

@@ -0,0 +1,42 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
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 convertMessages = (messages) => {
return messages.map((item) => ({
alt: "Tekst",
src: item.interlocutorData.image,
title: item.interlocutorData.name,
text: item?.chat?.messages[0]?.text,
}));
};

export const MyMessages = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const userId = useSelector(selectUserId);
const chats = useSelector(selectLatestChats);
const [lastChats, setLastChats] = useState([]);

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

+ 18
- 0
src/components/Popovers/MyMessages/MyMessages.styled.js Wyświetl plik

@@ -0,0 +1,18 @@
import { TextField } from "@mui/material";
import styled from "styled-components";

export const SearchInput = styled(TextField)`
margin-left: 3.8rem;
background-color: #F4F4F4;
width: 45%;
max-width: 100%;
@media (max-width: 1100px) {
width: 36%;
}
@media (max-width: 900px) {
width: 54%;
}
@media (max-width: 600px) {
width: 36%;
}
`

+ 74
- 0
src/components/Popovers/MyPosts/MyPosts.js Wyświetl plik

@@ -0,0 +1,74 @@
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>)
// }
// ]
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 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={arrayOfMineOffers}
buttonText={t("header.checkEverything")}
/>
);
};

+ 29
- 0
src/components/Popovers/MyPosts/MyPosts.styled.js Wyświetl plik

@@ -0,0 +1,29 @@
import { TextField, Avatar } from "@mui/material";
import Suit from '@mui/icons-material/WorkOutline';
import styled from "styled-components";

export const PostsImgSuit = styled(Suit)`
width: 1rem;
height: 1rem;
margin-right: .36rem;
`

export const PostsAvatar = styled(Avatar)`
border-radius: 4px;
`

export const SearchInput = styled(TextField)`
margin-left: 3.8rem;
background-color: #F4F4F4;
width: 45%;
max-width: 100%;
@media (max-width: 1100px) {
width: 36%;
}
@media (max-width: 900px) {
width: 54%;
}
@media (max-width: 600px) {
width: 36%;
}
`

+ 62
- 0
src/components/Popovers/MyProfile/MyProfile.js Wyświetl plik

@@ -0,0 +1,62 @@
import React, { useEffect, useState } from "react";
import { LogoutIcon, ProfileImgPIB } from "./MyProfile.styled";
import HeaderPopover from "../HeaderPopover/HeaderPopover";
import { useTranslation } from "react-i18next";
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 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={profileAsArray}
buttonText={t("header.checkProfile")}
buttonIcon={<EyeIcon color={selectedTheme.iconYellowColor} />}
isProfile
secondButtonIcon={<LogoutIcon color={selectedTheme.iconYellowColor} />}
secondButtonText={"Odjavite se"}
secondButtonOnClick={handleLogout}
/>
);
};

+ 34
- 0
src/components/Popovers/MyProfile/MyProfile.styled.js Wyświetl plik

@@ -0,0 +1,34 @@
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;
height: 1rem;
margin-right: .36rem;
`

export const ProfileAvatar = styled(Avatar)`
width: 63px;
height: 63px;
margin-right: 1rem;
`

export const SearchInput = styled(TextField)`
margin-left: 3.8rem;
background-color: #F4F4F4;
width: 45%;
max-width: 100%;
@media (max-width: 1100px) {
width: 36%;
}
@media (max-width: 900px) {
width: 54%;
}
@media (max-width: 600px) {
width: 36%;
}
`
export const LogoutIcon = styled(Logout)`
`

+ 32
- 0
src/components/Popovers/PopoverComponent.js Wyświetl plik

@@ -0,0 +1,32 @@
import React from "react";
import PropTypes from "prop-types";
import { Popover } from "@mui/material";

const PopoverComponent = ({ open, anchorEl, onClose, content }) => {
const handleClose = () => {
onClose();
};

return (
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
{content}
</Popover>
);
};

PopoverComponent.propTypes = {
anchorEl: PropTypes.object,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
content: PropTypes.any,
};

export default PopoverComponent;

+ 1
- 1
src/components/Radio/Button/RadioButton.js Wyświetl plik

@@ -37,7 +37,7 @@ const RadioButton = (props) => {

RadioButton.propTypes = {
children: PropTypes.node,
value: PropTypes.number,
value: PropTypes.any,
label: PropTypes.string,
number: PropTypes.number,
fullWidth: PropTypes.bool,

+ 6
- 1
src/components/Router/PrivateRoute.js Wyświetl plik

@@ -1,18 +1,23 @@
import React, { useEffect } from 'react';
import { Redirect, Route } from 'react-router';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { authenticateUser } from '../../store/actions/login/loginActions';
// import { selectIsUserAuthenticated } from '../../store/selectors/userSelectors';
import { LOGIN_PAGE } from '../../constants/pages';
import { fetchProfile } from '../../store/actions/profile/profileActions';
import { selectUserId } from '../../store/selectors/loginSelectors';

const PrivateRoute = ({ ...props }) => {
const dispatch = useDispatch();
const userId = useSelector(selectUserId);
// const isUserAuthenticated = useSelector(selectIsUserAuthenticated);
const isUserAuthenticated = true;

useEffect(() => {
if (!isUserAuthenticated) {
dispatch(authenticateUser());
} else {
dispatch(fetchProfile(userId))
}
}, [isUserAuthenticated]); // eslint-disable-line


+ 0
- 1
src/components/Scroller/HorizontalScroller.styled.js Wyświetl plik

@@ -47,7 +47,6 @@ export const ListContainer = styled(ScrollContainer)`
flex: 1;
flex-direction: row;
flex-wrap: nowrap;
cursor: grab;
scroll-behavior: smooth;
margin: 0 18px;
user-select: none;

+ 6
- 2
src/components/Select/Option/Option.js Wyświetl plik

@@ -3,9 +3,8 @@ import PropTypes from 'prop-types'
import { OptionIcon, OptionStyled } from './Option.styled'

const Option = props => {
console.log(props)
return (
<OptionStyled {...props}>
<OptionStyled {...props} value={props.value} >
{props.startIcon ? (
<OptionIcon color={props.color}>
{props.startIcon}
@@ -22,6 +21,11 @@ Option.propTypes = {
children: PropTypes.node,
color: PropTypes.any,
startIcon: PropTypes.any,
value: PropTypes.any,
// selected: PropTypes.bool,
}
Option.defaultProps = {
// selected: true
}

export default Option

+ 12
- 4
src/components/Select/Select.js Wyświetl plik

@@ -1,15 +1,22 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { SelectIcon, SelectStyled } from "./Select.styled";
import { SelectStyled, SelectIcon } from "./Select.styled";
import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg";

// import {Select as SelectMUI} from "@mui/material";

const Select = (props) => {
const [isOpened, setIsOpened] = useState(false);
const handleOpen = () => {
setIsOpened(prevState => !prevState);
}
return (
<SelectStyled
defaultValue={props.defaultValue}
defaultValue={props.defaultValue}
fullWidth={props.fullwidth}
open={isOpened}
onClick={() => handleOpen()}
value={props.value}
width={props.width}
height={props.height}
className={props.className}
@@ -33,10 +40,11 @@ Select.propTypes = {
defaultValue: PropTypes.number,
className: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.any,
};
Select.defaultProps = {
fullwidth: true,
height: "48px"
height: "48px",
};

export default Select;

+ 8
- 2
src/components/Select/Select.styled.js Wyświetl plik

@@ -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};
@@ -8,9 +9,14 @@ export const SelectStyled = styled(Select)`
font-size: 16px;
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;
top: 0px;
left: -15px;
top: -1px;
left: -8px;
cursor: pointer;
`

+ 11
- 7
src/components/TextFields/TextField/TextField.js Wyświetl plik

@@ -1,20 +1,18 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useState } from "react";
import { TextFieldContainer, TextFieldStyled } from "./TextField.styled";
import PropTypes from "prop-types";

export const TextField = (props) => {
export const TextField = React.forwardRef((props, ref) => {
const [isFieldEmpty, setIsFieldEmpty] = useState(true);

// for italicPlaceholder
useEffect(() => {
if (props.value.length === 0) {
if (props?.value?.length === 0) {
setIsFieldEmpty(true);
} else {
setIsFieldEmpty(false);
}
}, [props.value]);
const textInputRef = useRef();

return (
<TextFieldContainer
style={props.containerStyle}
@@ -22,6 +20,7 @@ export const TextField = (props) => {
height={props.height}
>
<TextFieldStyled
inputRef={ref}
placeholder={props.placeholder}
width={props.width}
height={props.height}
@@ -41,17 +40,20 @@ export const TextField = (props) => {
InputProps={props.InputProps}
sx={props.style}
label={props.showAnimation ? props.placeholder : ""}
onFocus={props.onFocus}
onBlur={props.onBlur}
italicplaceholder={(props.italicPlaceholder && isFieldEmpty) ? "true" : "false"}
ref={textInputRef}
focused={props.focused}
>
{props.children}
</TextFieldStyled>
</TextFieldContainer>
);
};
});

TextField.displayName = "TextField";

TextField.propTypes = {
history: PropTypes.shape({
@@ -85,6 +87,8 @@ TextField.propTypes = {
ref: PropTypes.any,
minRows: PropTypes.number,
multiline: PropTypes.bool,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
focused: PropTypes.bool,
InputProps: PropTypes.shape({
startAdornment: PropTypes.node,

+ 17
- 0
src/enums/conditionEnum.js Wyświetl plik

@@ -0,0 +1,17 @@
export const conditionSelectEnum = {
NEW: {
value: 1,
mainText: "Novo",
altText: ""
},
USED: {
value: 2,
mainText: "Polovno ",
altText: " (korišćeno)"
},
LIKE_NEW: {
value: 3,
mainText: "Kao novo",
altText: " (nekorišćeno)"
}
}

+ 22
- 0
src/enums/sortEnum.js Wyświetl plik

@@ -0,0 +1,22 @@
export const sortEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: ""
},
POPULAR: {
value: 1,
mainText: "Najpopularnije",
queryString: "popular"
},
NEW: {
value: 2,
mainText: "Najnovije",
queryString: "newest"
},
OLD: {
value: 3,
mainText: "Najstarije",
queryString: "oldest"
}
}

+ 255
- 0
src/hooks/useFilters.js Wyświetl plik

@@ -0,0 +1,255 @@
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
// import { useHistory } from "react-router-dom";
import { fetchCategories } from "../store/actions/categories/categoriesActions";
import {
setFilteredCategory,
setFilteredLocations,
setFilteredSubcategory,
setIsAppliedStatus,
// setQueryString,
} from "../store/actions/filters/filtersActions";
import { fetchLocations } from "../store/actions/locations/locationsActions";
import {
selectCategories,
selectSubcategories,
} from "../store/selectors/categoriesSelectors";
import {
selectAppliedStatus,
// selectQueryString,
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSubcategory,
} from "../store/selectors/filtersSelectors";
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 qs from "query-string";
import { useQueryString } from "./useQueryString";
// import qs from "query-string";



const useFilters = () => {
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 categories = useSelector(selectCategories);
const subcategories = useSelector(
selectSubcategories(selectedCategory?.name)
);
const locations = useSelector(selectLocations);
const dispatch = useDispatch();
const queryStringHook = useQueryString();
useEffect(() => {
if (!loaded) {
dispatch(fetchCategories());
dispatch(fetchLocations());
setLoadedStatus(true);
}
}, [categories, locations]);

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

useEffect(() => {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (categories?.length > 0 && locations?.length > 0) {
let category;
if (queryObject.has("category")) {
category = categories.find(
(item) => item.name === queryObject.get("category").toString()
);
setSelectedCategory(category);
}
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 === item)
);
});
// } else {
// locationsToPush.push(
// locations.find((p) => p.city === queryObject.location)
// );
// }
setSelectedLocations([...locationsToPush]);
}
}
}, [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 = () => {
// 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
const clearFilters = () => {
setSelectedLocations([]);
setSelectedSubcategory();
setSelectedCategory();
};

// 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) {
sum++;
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
sum++;
}
if (selectedLocations && selectedLocations?.length > 0) {
sum += selectedLocations.length;
}
return sum;
};

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

export default useFilters;

+ 8
- 0
src/hooks/usePaging.js Wyświetl plik

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

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

+ 182
- 0
src/hooks/useQueryString.js Wyświetl plik

@@ -0,0 +1,182 @@
import _ from "lodash";
import { useEffect, useState } from "react";
// import _ from "lodash"
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../constants/pages";
import { setQueryString as setQueryStringSaga } from "../store/actions/queryString/queryStringActions";
import { selectQueryString } from "../store/selectors/queryStringSelectors";
// import useFilters from "./useFilters";
// import useSorting from "./useSorting";
// import { sortEnum } from "../enums/sortEnum";
import { convertQueryStringBackend, convertQueryStringFrontend } from "../util/helpers/queryHelpers";

export const useQueryString = () => {
const queryString = useSelector(selectQueryString);
const history = useHistory();
const [globalQueryString, setGlobalQueryString] = useState("");
const [initial, setInitial] = useState(true);
const [loadedFromURL, setLoadedFromURL] = useState(false);
// const [isFetched, setIsFetched] = useState(false);
const dispatch = useDispatch();
// const sorting = useSorting();

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

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

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

useEffect(() => {
if (!initial) {
history.push({
pathname: HOME_PAGE,
search: "?" + globalQueryString,
});
}
}, [globalQueryString, initial]);

const getQueryString = () => {
return queryString;
};
const setQueryString = (newQueryString) => {
dispatch(setQueryStringSaga(newQueryString));
};
const getQueryObject = () => {
const urlParams = new URLSearchParams(queryString);
return Object.fromEntries(urlParams);
};
const appendToQueryString = (key, value) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (key === "location") {
if (urlParams.has(key)) {
let arrayOfLocations = urlParams.getAll(key);
if (arrayOfLocations.includes(value)) {
arrayOfLocations = arrayOfLocations.filter(
(item) => item?.toString() !== value?.toString()
);
urlParams.delete(key);
arrayOfLocations.forEach((item) => {
urlParams.append(key, item);
});
}
}
} else {
if (urlParams.has(key)) {
urlParams.delete(key);
}
}
if (!value) setQueryString(urlParams.toString());
urlParams.append(key, value);
setQueryString(urlParams.toString());
return urlParams.toString();
}
};
const appendMultipleToQueryString = (array = []) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (
array.find((item) => item.key === "category") ||
array.find((item) => item.key === "subcategory")
) {
urlParams.delete("location");
}
array.forEach((item) => {
// if (item.key === "location") {
// if (urlParams.has(item.key)) {
// let arrayOfLocations = urlParams.getAll(item.key);
// if (arrayOfLocations.includes(item.value)) {
// arrayOfLocations = arrayOfLocations.filter(
// (itemInList) =>
// itemInList?.toString() !== item?.value?.toString()
// );
// urlParams.delete(item.key);
// arrayOfLocations.forEach((location) => {
// urlParams.append(item.key, location);
// });
// }
// }
// } else {
if (urlParams.has(item.key) && item.key !== "location") {
urlParams.delete(item.key);
}
// }
console.log("item.key: ", item.key);
console.log("item.value: ", item.value);
if (!item.value) return;
urlParams.append(item.key, item.value);
console.log("querytString: ", urlParams.toString())
});
console.log("postavlja se");
setQueryString(urlParams.toString());
return urlParams.toString();
}
};
const deleteFromQueryString = (key, value = null) => {
let urlParams = new URLSearchParams(queryString);
if (key === "location") {
let arrayOfLocations = urlParams.getAll(key);
arrayOfLocations = arrayOfLocations.filter((item) => item !== value);
urlParams.delete(key);
arrayOfLocations.forEach((item) => {
urlParams.append(key, item);
});
} else if (key === "sortBy") {
urlParams.delete("_des_date");
urlParams.delete("_des_popular");
} else {
urlParams.delete(key);
}
setQueryString(urlParams.toString());
return urlParams.toString();
};
const getInitialQueryString = () => {
let urlParams = new URLSearchParams(queryString);
urlParams = new URLSearchParams(appendToQueryString("size", 10));
urlParams = new URLSearchParams(appendToQueryString("page", 1));
return urlParams;
};

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

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

+ 30
- 0
src/hooks/useScreenDimensions.js Wyświetl plik

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

+ 21
- 0
src/hooks/useSearch.js Wyświetl plik

@@ -0,0 +1,21 @@
// 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 queryStringHook = useQueryString();
// const queryString = useSelector(selectQueryString);
const searchOffers = (searchString) => {
queryStringHook.appendToQueryString("oname", searchString);
// const queryObject = new URLSearchParams(queryString);
// queryObject.set('oname', searchString);
// dispatch(fetchOffers({queryString: "?" + queryObject.toString()}));
}

return {
searchOffers
}
}

+ 76
- 0
src/hooks/useSorting.js Wyświetl plik

@@ -0,0 +1,76 @@
// 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 { sortEnum } from "../enums/sortEnum";
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 = () => {
const dispatch = useDispatch();
const selectedSortOption = useSelector(selectSelectedSortOption);
const sortOptions = sortEnum;
const queryStringHook = useQueryString();

useEffect(() => {
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);
}
if (queryObject.get("sortBy") === "oldest") {
setSelectedSortOption(sortEnum.OLD);
}
if (queryObject.get("sortBy") === "popular") {
setSelectedSortOption(sortEnum.POPULAR);
}
}
}, [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.value === sortOptions.NEW.value) {
_des_date = true;
}
if (payload.value === sortOptions.OLD.value) {
_des_date = false;
}
if (payload.value === sortOptions.POPULAR.value) {
_des_popular = true;
}
if (_des_date !== null) {
queryStringHook.appendMultipleToQueryString([
{ key: "_des_date", value: `${_des_date}` },
{ key: "_des_popular" },
]);
}
if (_des_popular !== null) {
queryStringHook.appendMultipleToQueryString([
{ key: "_des_popular", value: `${_des_popular}` },
{ key: "_des_date" },
]);
}
};

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

+ 13
- 1
src/i18n/resources/rs.js Wyświetl plik

@@ -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",
@@ -141,5 +143,15 @@ export default {
},
apiErrors: {
somethingWentWrong: "Greska sa serverom!"
}
},
header: {
addOffer: "Dodaj proizvod",
searchOffers: "Pretražite proizvode...",
myProfile: "Moj profil",
checkProfile: "POGLEDAJ PROFIL",
myOffers: "Moje objave",
checkEverything: "POGLEDAJ SVE",
myMessages: "Moje poruke",
newOffers: "Najnovije ponude"
},
};

+ 1
- 0
src/layouts/MainLayout/MainLayout.styled.js Wyświetl plik

@@ -10,6 +10,7 @@ export const MainLayoutContainer = styled(Container)`
display: flex;
flex: 1;
height: 100%;
margin-top: 80px;
`

export const LeftCard = styled(Grid)`

+ 1
- 0
src/layouts/ProfileLayout/ProfileLayout.styled.js Wyświetl plik

@@ -10,6 +10,7 @@ export const ProfileLayoutContainer = styled(Container)`
max-width: none;
flex: 1;
height: 100%;
margin-top: 80px;
`

export const LeftCard = styled(Grid)`

+ 1
- 1
src/pages/ForgotPasswordPage/ForgotPassword.styled.js Wyświetl plik

@@ -67,4 +67,4 @@ export const LoginTextContainer = styled(Box)`
@media (max-height: 800px) {
margin-top: 26px;
}
`;
`;

+ 8
- 53
src/pages/HomePage/HomePageMUI.js Wyświetl plik

@@ -1,68 +1,23 @@
import React, { useEffect } from "react";
import Navbar from "../../components/MUI/NavbarComponent";
import React from "react";
// import Navbar from "../../components/MUI/NavbarComponent";
// import FilterCard from "../../components/Cards/FilterCard/FilterCard";
import { HomePageContainer } from "./HomePage.styled";
// import MarketPlace from "../../components/MarketPlace/MarketPlace";
// import MainLayout from "../../layouts/MainLayout/MainLayout";
import { useDispatch } from "react-redux";
// import { logoutUser } from "../../store/actions/login/loginActions";
import Mockupdata from "../../components/Cards/FilterCard/Mockupdata";
import qs from "query-string";
import { useHistory } from "react-router-dom";
import { setFilters } from "../../store/actions/filters/filtersActions";
// import { useDispatch } from "react-redux";
// // import { logoutUser } from "../../store/actions/login/loginActions";
// import Mockupdata from "../../components/Cards/FilterCard/Mockupdata";
// import qs from "query-string";
// import { useHistory } from "react-router-dom";
// import { setFilters } from "../../store/actions/filters/filtersActions";
// import ProfileLayout from "../../layouts/ProfileLayout/ProfileLayout";
import FilterCard from "../../components/Cards/FilterCard/FilterCard";
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>
<Navbar />
<MainLayout leftCard={<FilterCard />} content={<MarketPlace />} />
</HomePageContainer>
);

+ 1
- 0
src/pages/ItemDetailsPage/ItemDetailsPage.styled.js Wyświetl plik

@@ -4,6 +4,7 @@ import selectedTheme from "../../themes";
export const ItemDetailsPageContainer = styled(Container)`
padding: 0;
margin: 0;
margin-top: 80px;
height: 100%;
width: 100%;
max-width: none;

+ 2
- 2
src/pages/ItemDetailsPage/ItemDetailsPageMUI.js Wyświetl plik

@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { PropTypes } from "prop-types";
import Navbar from "../../components/MUI/NavbarComponent";
// import Navbar from "../../components/MUI/NavbarComponent";
import { ItemDetailsPageContainer } from "./ItemDetailsPage.styled";
import { useDispatch, useSelector } from "react-redux";
import ItemDetails from "../../components/ItemDetails/ItemDetails";
@@ -33,7 +33,7 @@ const ItemDetailsPage = (props) => {

return (
<ItemDetailsPageContainer>
<Navbar />
{/* <Navbar /> */}
{/* right card mora mi bude Review Card */}
<ItemDetailsLayout content={<ItemDetails />} rightCard={<UserReviewsCard />} />


+ 7
- 4
src/pages/LoginPage/LoginPage.js Wyświetl plik

@@ -8,9 +8,9 @@ import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import {
clearLoginErrors,
fetchUser,
fetchLogin,
} from "../../store/actions/login/loginActions";
import { selectLoginError } from "../../store/selectors/loginSelectors";
import { selectLoginError, selectUserId } from "../../store/selectors/loginSelectors";
import { FORGOT_PASSWORD_PAGE, HOME_PAGE } from "../../constants/pages";
import { ReactComponent as VisibilityOn } from "../../assets/images/svg/eye-striked.svg";
import { ReactComponent as VisibilityOff } from "../../assets/images/svg/eye.svg";
@@ -34,11 +34,13 @@ import {
import selectedTheme from "../../themes";
import loginValidation from "../../validations/loginValidation";
import loginInitialValues from "../../initialValues/loginInitialValues";
import { fetchProfile } from "../../store/actions/profile/profileActions";

const LoginPage = ({ history }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const error = useSelector(selectLoginError);
const userId = useSelector(selectUserId);

const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword(!showPassword);
@@ -64,6 +66,7 @@ const LoginPage = ({ history }) => {
}, []);

const handleApiResponseSuccess = () => {
dispatch(fetchProfile(userId))
history.push({
pathname: HOME_PAGE,
state: {
@@ -73,10 +76,10 @@ const LoginPage = ({ history }) => {
};

const handleSubmit = (values) => {
const { email, password: password } = values;
const { email, password } = values;
dispatch(clearLoginErrors());
dispatch(
fetchUser({
fetchLogin({
email,
password,
handleApiResponseSuccess,

+ 0
- 3
src/pages/RegisterPages/Register/FirstPart/FirstPartOfRegistration.js Wyświetl plik

@@ -89,16 +89,13 @@ const FirstPartOfRegistration = (props) => {
}}
/>


{formik.errors.mail && formik.touched.mail ? (
<ErrorMessage>{formik.errors.mail}</ErrorMessage>
) : formik.errors.password && formik.touched.password ? (

<ErrorMessage>{formik.errors.password}</ErrorMessage>
) : (
<></>
)}

{props.error && <ErrorMessage>{props.errorMessage}</ErrorMessage>}

<PrimaryButton

+ 2
- 2
src/pages/RegisterPages/Register/Register.styled.js Wyświetl plik

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

+ 6
- 3
src/request/apiEndpoints.js Wyświetl plik

@@ -118,6 +118,7 @@ export default {
'/users?fp={fp}&offer={offer}&landingPageUrl={landingPageUrl}&registrationFlowType={registrationFlowType}',
updateUserRegistration: '/users/{userUid}',
invite: '/users/invite',
getProfile: 'users/'
},
applications: {
application: '/applications/{applicationUid}',
@@ -158,8 +159,10 @@ export default {
setFingerprint: '/affiliate/fingerprint',
},
offers: {
getOffers: '/offers',
getOneOffer: '/offers',
addOffer: '/offers',
getOffers: 'offers',
addOffer: 'offers',
categories: 'categories',
locations: 'locations',
mineOffers: 'users'
}
};

+ 5
- 0
src/request/categoriesRequest.js Wyświetl plik

@@ -0,0 +1,5 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchCategories = () =>
getRequest(apiEndpoints.offers.categories);

+ 8
- 0
src/request/chatRequest.js Wyświetl plik

@@ -0,0 +1,8 @@
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`)
}

+ 4
- 2
src/request/index.js Wyświetl plik

@@ -3,6 +3,7 @@ import queryString from "qs";

const request = axios.create({
baseURL: "http://192.168.88.150:3001/",
// baseURL: "http://192.168.88.176:3001/",
headers: {
"Content-Type": "application/json",
},
@@ -11,8 +12,9 @@ const request = axios.create({
queryString.stringify(params, { arrayFormat: "comma" }),
});

export const getRequest = (url, params = null, options = null) =>
request.get(url, { params, ...options });
export const getRequest = (url, params = null, options = null) => {
return request.get(url, { params, ...options });
}

export const postRequest = (url, data, params = null, options = null) =>
request.post(url, data, { params, ...options });

+ 5
- 0
src/request/locationsRequest.js Wyświetl plik

@@ -0,0 +1,5 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchLocations = () =>
getRequest(apiEndpoints.offers.locations);

+ 9
- 1
src/request/offersRequest.js Wyświetl plik

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

export const attemptFetchOffers = () => {
export const attemptFetchOffers = (payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload)
return getRequest(apiEndpoints.offers.getOffers)
}
export const attemptFetchOneOffer = (payload) => {
@@ -9,6 +10,13 @@ export const attemptFetchOneOffer = (payload) => {
const url = `${apiEndpoints.offers.getOneOffer}/${payload.payload}`;
return getRequest(url);
}
export const attemptFetchMoreOffers = (page, payload) => {
if (payload) return getRequest(apiEndpoints.offers.getOffers + payload + `&size=10&page=${page}`);
return getRequest(apiEndpoints.offers.getOffers + `?size=10&page=${page}`);
}
export const attemptAddOffer = (payload) => {
return postRequest(apiEndpoints.offers.addOffer, payload)
}
export const attemptFetchMineOffers = (payload) => {
return getRequest(`${apiEndpoints.offers.mineOffers}/${payload}/offers`)
}

+ 5
- 0
src/request/profileRequest.js Wyświetl plik

@@ -0,0 +1,5 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchProfile = (payload) =>
getRequest(apiEndpoints.users.getProfile + payload);

+ 6
- 0
src/store/actions/categories/categoriesActionConstants.js Wyświetl plik

@@ -0,0 +1,6 @@
import { createFetchType } from "../actionHelpers";

const CATEGORIES_SCOPE = "CATEGORIES";
export const CATEGORIES_FETCH = createFetchType(CATEGORIES_SCOPE);

export const CATEGORIES_SET = "CATEGORIES_SET";

+ 10
- 0
src/store/actions/categories/categoriesActions.js Wyświetl plik

@@ -0,0 +1,10 @@
import { CATEGORIES_FETCH, CATEGORIES_SET } from "./categoriesActionConstants";

export const fetchCategories = () => ({
type: CATEGORIES_FETCH
})

export const setCategories = (payload) => ({
type: CATEGORIES_SET,
payload
})

+ 9
- 0
src/store/actions/chat/chatActionConstants.js Wyświetl plik

@@ -0,0 +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";

+ 14
- 0
src/store/actions/chat/chatActions.js Wyświetl plik

@@ -0,0 +1,14 @@
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,
})

+ 4
- 1
src/store/actions/filters/filtersActionConstants.js Wyświetl plik

@@ -5,4 +5,7 @@ export const SET_FILTERS = createSetType(FILTERS_SCOPE);
export const CLEAR_FILTERS = createClearType(FILTERS_SCOPE);
export const SET_CATEGORY = "FILTERS_SET_CATEGORY";
export const SET_SUBCATEGORY = "FILTERS_SET_SUBCATEGORY";
export const SET_CITIES = "FILTERS_SET_CITIES";
export const SET_LOCATIONS = "FILTERS_SET_LOCATIONS";
export const SET_SORT_OPTION = "FILTERS_SET_SORT_OPTION";
export const SET_IS_APPLIED = "FILTERS_SET_IS_APPLIED";
export const SET_QUERY_STRING = "FILTERS_SET_QUERY_STRING";

+ 17
- 5
src/store/actions/filters/filtersActions.js Wyświetl plik

@@ -1,4 +1,4 @@
import { CLEAR_FILTERS, SET_CATEGORY, SET_CITIES, SET_FILTERS, SET_SUBCATEGORY } from "./filtersActionConstants";
import { CLEAR_FILTERS, SET_CATEGORY, SET_FILTERS, SET_IS_APPLIED, SET_LOCATIONS, SET_QUERY_STRING, SET_SORT_OPTION, SET_SUBCATEGORY } from "./filtersActionConstants";

export const setFilters = (payload) => ({
type: SET_FILTERS,
@@ -7,15 +7,27 @@ export const setFilters = (payload) => ({
export const clearFilters = () => ({
type: CLEAR_FILTERS
})
export const setCategory = (payload) => ({
export const setFilteredCategory = (payload) => ({
type: SET_CATEGORY,
payload
})
export const setSubcategory = (payload) => ({
export const setFilteredSubcategory = (payload) => ({
type: SET_SUBCATEGORY,
payload
})
export const setCities = (payload) => ({
type: SET_CITIES,
export const setFilteredLocations = (payload) => ({
type: SET_LOCATIONS,
payload
})
export const setFilteredSortOption = (payload) => ({
type: SET_SORT_OPTION,
payload
})
export const setIsAppliedStatus = (payload) => ({
type: SET_IS_APPLIED,
payload,
})
export const setQueryString = (payload) => ({
type: SET_QUERY_STRING,
payload,
})

+ 6
- 0
src/store/actions/locations/locationsActionConstants.js Wyświetl plik

@@ -0,0 +1,6 @@
import { createFetchType } from "../actionHelpers";

const LOCATIONS_SCOPE = "LOCATIONS_SCOPE";
export const LOCATIONS_FETCH = createFetchType(LOCATIONS_SCOPE);

export const LOCATIONS_SET = "LOCATIONS_SET";

+ 10
- 0
src/store/actions/locations/locationsActions.js Wyświetl plik

@@ -0,0 +1,10 @@
import { LOCATIONS_FETCH, LOCATIONS_SET } from "./locationsActionConstants";

export const fetchLocations = () => ({
type: LOCATIONS_FETCH,
});

export const setLocations = (payload) => ({
type: LOCATIONS_SET,
payload
})

+ 3
- 2
src/store/actions/login/loginActions.js Wyświetl plik

@@ -14,7 +14,7 @@ import {
} from './loginActionConstants';


export const fetchUser = (payload) => ({
export const fetchLogin = (payload) => ({
type: LOGIN_USER_FETCH,
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) => ({

+ 13
- 1
src/store/actions/offers/offersActionConstants.js Wyświetl plik

@@ -3,6 +3,11 @@ import { createClearType, createErrorType, createFetchType, createSuccessType }
const OFFERS_SCOPE = "OFFERS_SCOPE";
const ONE_OFFER_SCOPE = "ONE_OFFER_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);
@@ -12,7 +17,14 @@ export const ONE_OFFER_FETCH = createFetchType(ONE_OFFER_SCOPE);
export const ONE_OFFER_SUCCESS = createSuccessType(ONE_OFFER_FETCH);
export const ONE_OFFER_ERROR = createErrorType(ONE_OFFER_SCOPE);



export const OFFERS_PINNED_SET = "OFFERS_PINNED_SET";
export const OFFERS_PINNED_ADD = "OFFERS_PINNED_ADD";
export const OFFERS_SET = "OFFERS_SET";
export const OFFER_SET = "OFFER_SET"
export const OFFERS_ADD = "OFFERS_ADD";
export const OFFER_ADD = "OFFER_ADD";
export const OFFERS_NO_MORE = "OFFERS_NO_MORE";
export const OFFERS_SET_TOTAL = "OFFERS_SET_TOTAL";
export const OFFERS_MINE_SET = "OFFERS_MY_ADD";
export const OFFER_ADD = "OFFER_ADD";

+ 63
- 16
src/store/actions/offers/offersActions.js Wyświetl plik

@@ -1,24 +1,51 @@
import { OFFERS_ADD, OFFERS_CLEAR, OFFERS_ERROR, OFFERS_FETCH, OFFERS_SET, OFFERS_SUCCESS, OFFER_ADD, ONE_OFFER_FETCH, ONE_OFFER_SUCCESS, ONE_OFFER_ERROR, OFFER_SET } 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,
OFFER_ADD,
OFFER_SET,
ONE_OFFER_ERROR,
ONE_OFFER_FETCH,
ONE_OFFER_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,
});
export const addPinnedOffers = (payload) => ({
type: OFFERS_PINNED_ADD,
payload,
});
export const addOffers = (payload) => ({
type: OFFERS_ADD,
payload
@@ -46,4 +73,24 @@ export const fetchOneOfferSuccess = (payload) => ({
export const setOffer = (payload) => ({
type: OFFER_SET,
payload
})
})

export const fetchMoreOffers = (payload) => ({
type: OFFERS_FETCH_MORE,
payload,
});
export const setNoMoreOffersStatus = (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,
});

+ 8
- 0
src/store/actions/profile/profileActionConstants.js Wyświetl plik

@@ -0,0 +1,8 @@
import { createErrorType, createFetchType, createSuccessType } from "../actionHelpers";

const PROFILE_SCOPE = "PROFILE_SCOPE";
export const PROFILE_FETCH = createFetchType(PROFILE_SCOPE);
export const PROFILE_SUCCESS = createSuccessType(PROFILE_SCOPE);
export const PROFILE_ERROR = createErrorType(PROFILE_SCOPE);

export const PROFILE_SET = "PROFILE_SET";

+ 19
- 0
src/store/actions/profile/profileActions.js Wyświetl plik

@@ -0,0 +1,19 @@
import { PROFILE_ERROR, PROFILE_FETCH, PROFILE_SET, PROFILE_SUCCESS } from "./profileActionConstants";

export const fetchProfile = (payload) => ({
type: PROFILE_FETCH,
payload
})
export const fetchSuccessProfile = (payload) => ({
type: PROFILE_SUCCESS,
payload,
})

export const fetchErrorProfile = (payload) => ({
type: PROFILE_ERROR,
payload,
})
export const setProfile = (payload) => ({
type: PROFILE_SET,
payload,
})

+ 2
- 0
src/store/actions/queryString/queryStringActionConstants.js Wyświetl plik

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

+ 10
- 0
src/store/actions/queryString/queryStringActions.js Wyświetl plik

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

+ 5
- 3
src/store/middleware/accessTokensMiddleware.js Wyświetl plik

@@ -14,6 +14,7 @@ import { logoutUser, refreshUserToken } from "../actions/login/loginActions";

//Change URL with .env
const baseURL = "http://192.168.88.150:3001/";
// const baseURL = "http://192.168.88.175:3005/";

//Interceptor unique name
export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR";
@@ -25,12 +26,12 @@ export default ({ dispatch }) =>
const jwtToken = authScopeStringGetHelper(JWT_TOKEN);
const refresh = authScopeStringGetHelper(JWT_REFRESH_TOKEN);
if (!jwtToken || !refresh) return Promise.resolve(response);
const jwtTokenDecoded = jwt.decode(jwtToken);
const refreshTokenDecoded = jwt.decode(refresh);
if (!response.headers?.Authorization) {
response.headers.Authorization = `Bearer ${jwtToken}`;
}
const jwtTokenDecoded = jwt.decode(jwtToken);
const refreshTokenDecoded = jwt.decode(refresh);

// If refresh token is expired, log out user
if (new Date() > new Date(refreshTokenDecoded?.exp * 1000)) {
dispatch(logoutUser());
@@ -43,6 +44,7 @@ export default ({ dispatch }) =>
const newToken = axiosResponse.data.token;
dispatch(refreshUserToken(newToken));
}
return Promise.resolve(response);
}, accessTokensMiddlewareInterceptorName);


+ 20
- 0
src/store/reducers/categories/categoriesReducer.js Wyświetl plik

@@ -0,0 +1,20 @@
import { CATEGORIES_SET } from "../../actions/categories/categoriesActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
categories: [],
};

export default createReducer(
{
[CATEGORIES_SET]: setCategories
},
initialState
);

function setCategories(state, action) {
return {
...state,
categories: action.payload
}
}

+ 20
- 0
src/store/reducers/chat/chatReducer.js Wyświetl plik

@@ -0,0 +1,20 @@
import { CHAT_SET } from "../../actions/chat/chatActionConstants"
import createReducer from "../../utils/createReducer"

const initialState = {
latestChats: [],
}

export default createReducer(
{
[CHAT_SET]: setChats,
},
initialState
)

function setChats(state, action) {
return {
...state,
latestChats: action.payload
}
}

+ 34
- 9
src/store/reducers/filters/filtersReducer.js Wyświetl plik

@@ -1,8 +1,10 @@
import {
CLEAR_FILTERS,
SET_CATEGORY,
SET_CITIES,
SET_FILTERS,
SET_IS_APPLIED,
SET_LOCATIONS,
SET_SORT_OPTION,
SET_SUBCATEGORY,
} from "../../actions/filters/filtersActionConstants";
import createReducer from "../../utils/createReducer";
@@ -11,7 +13,10 @@ const initialState = {
filters: {
category: null,
subcategory: null,
cities: [],
locations: [],
sortOption: null,
isApplied: false,
queryString: "",
},
};

@@ -19,9 +24,11 @@ export default createReducer(
{
[SET_FILTERS]: setFilters,
[CLEAR_FILTERS]: clearFilters,
[SET_CATEGORY]: setCategory,
[SET_SUBCATEGORY]: setSubcategory,
[SET_CITIES]: setCities,
[SET_CATEGORY]: setFilteredCategory,
[SET_SUBCATEGORY]: setFilteredSubcategory,
[SET_LOCATIONS]: setFilteredLocations,
[SET_SORT_OPTION]: setFilteredSortOption,
[SET_IS_APPLIED]: setIsAppliedStatus,
},
initialState
);
@@ -37,7 +44,7 @@ function clearFilters() {
return initialState;
}

function setCategory(state, { payload }) {
function setFilteredCategory(state, { payload }) {
return {
...state,
filters: {
@@ -47,7 +54,7 @@ function setCategory(state, { payload }) {
};
}

function setSubcategory(state, { payload }) {
function setFilteredSubcategory(state, { payload }) {
return {
...state,
filters: {
@@ -57,12 +64,30 @@ function setSubcategory(state, { payload }) {
};
}

function setCities(state, { payload }) {
function setFilteredLocations(state, { payload }) {
return {
...state,
filters: {
...state.filters,
cities: payload,
locations: payload,
},
};
}
function setFilteredSortOption(state, {payload}) {
return {
...state,
filters: {
...state.filters,
sortOption: payload,
}
}
}
function setIsAppliedStatus(state, {payload}) {
return {
...state,
filters: {
...state.filters,
isApplied: payload,
}
}
}

+ 0
- 0
src/store/reducers/index.js Wyświetl plik


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików

Ładowanie…
Anuluj
Zapisz