소스 검색

merge

global-styles
Pavle Golubovic 3 년 전
부모
커밋
8e6968bfef
100개의 변경된 파일3330개의 추가작업 그리고 771개의 파일을 삭제
  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. 47
    0
      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. 93
    13
      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. 81
    44
      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. 7
    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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 3
- 0
src/assets/images/svg/filter.svg 파일 보기

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

+ 5
- 0
src/assets/images/svg/log-out.svg 파일 보기

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

+ 43
- 0
src/assets/images/svg/logo-horizontal.svg 파일 보기

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

+ 47
- 0
src/assets/styles/_base.scss 파일 보기

@@ -0,0 +1,47 @@
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-anchor: none;
background-color: #F5F5F5;
}

* {
box-sizing: border-box;
}

html {
min-height: 100%;
font-size: 16px;

@include media-below($bp-xxl) {
font-size: 14px;
}

@include media-below($bp-xs) {
font-size: 13px;
}

@include media-below($bp-xxs) {
font-size: 10.5px;
}
}

html,
body,
#root {
@include flex-column;
flex: 1 0 auto;
}

input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
-webkit-appearance: none;
}

ul {
list-style: none;
padding: 0;
}

+ 1
- 1
src/components/Buttons/PrimaryButton/PrimaryButton.js 파일 보기

@@ -12,7 +12,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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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={selectedTheme.fonts.textFont} onClick={clearFilters}>
<Link
to="#"
textsize={"12px"}
font={selectedTheme.fonts.textFont}
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.colors.primaryPurple}
font={selectedTheme.fonts.textFont}
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 파일 보기

@@ -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 파일 보기

@@ -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.colors.primaryIconBackgroundColor,
backgroundColor: isOpened
? "white"
: selectedTheme.colors.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 파일 보기

@@ -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.colors.primaryPurple
: selectedTheme.colors.primaryText
!props.selected || props.selected?._id === 0
? selectedTheme.colors.primaryText
: selectedTheme.colors.primaryPurple
}
dropdownIcon={props.icon}
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
setIsOpened={setIsOpened}
open={props.open}
disabled={props.disabled}
setIsOpened={handleOpen}
toggleIconStyles={{
backgroundColor: isOpened ? "white" : selectedTheme.colors.primaryIconBackgroundColor,
backgroundColor: (
props.open !== undefined || props.open !== null
? props.open
: isOpened
)
? "white"
: selectedTheme.colors.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 파일 보기

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


+ 93
- 13
src/components/Cards/OfferCard/OfferCard.styled.js 파일 보기

@@ -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.colors.backgroundSponsoredColor : "white"};
props.sponsored === "true"
? selectedTheme.colors.backgroundSponsoredColor
: "white"};
border-radius: 4px;
${(props) =>
props.sponsored === 'true' &&
props.sponsored === "true" &&
`border: 1px solid ${selectedTheme.colors.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.colors.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: ${selectedTheme.fonts.textFont};
line-height: 22px;
font-size: 16px;
color: ${selectedTheme.colors.primaryDarkText};
color: ${selectedTheme.colors.primaryText};
@media (max-width: 600px) {
font-size: 14px;
position: relative;
left: -1px;
}
`;
export const OfferLocation = styled(Typography)`
font-family: ${selectedTheme.fonts.textFont};
color: ${selectedTheme.colors.primaryText};
font-family: "Open Sans";
color: ${selectedTheme.colors.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: ${selectedTheme.fonts.textFont};
color: ${selectedTheme.colors.primaryText};
line-height: 16px;
font-size: 12px;
@media (max-width: 1000px) {
display: none;
}
`;
export const OfferPackage = styled(Box)`
font-family: ${selectedTheme.fonts.textFont};
@@ -81,9 +118,6 @@ export const OfferViews = styled(Box)`
color: ${selectedTheme.colors.primaryText};
line-height: 16px;
font-size: 12px;
@media (max-width: 1200px) {
display: none;
}
`;
export const OfferDescriptionTitle = styled(Box)`
font-family: ${selectedTheme.fonts.textFont};
@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

export const ToggleIconOpened = styled(IconButton)`
cursor: pointer;
color: ${selectedTheme.colors.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.colors.primaryPurple};
&:hover button {
${(props) =>
props.disabled &&
`background-color: ${selectedTheme.colors.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.colors.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.colors.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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

+ 81
- 44
src/components/MarketPlace/Header/Header.styled.js 파일 보기

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

export const HeaderContainer = styled(Box)`
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
`
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
`;
export const HeaderLocation = styled(Box)`
font-family: ${selectedTheme.fonts.textFont};
color: ${selectedTheme.colors.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: ${selectedTheme.fonts.titleFont};
color: ${selectedTheme.colors.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: ${selectedTheme.fonts.textFont};
margin-top: 3px;
font-weight: 400;
position: relative;
left: -5px;
& div {
padding-left: 8px;
}
`
width: 210px;
height: 35px;
font-family: ${selectedTheme.fonts.textFont};
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: ${selectedTheme.fonts.textFont};
`
font-family: ${selectedTheme.fonts.textFont};
`;
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: ${selectedTheme.fonts.textFont};
font-size: 16px;
color: ${selectedTheme.colors.primaryText};
margin-left: 5px;
@media (max-width: 600px) {
display: none;
}
`

+ 2
- 2
src/components/MarketPlace/MarketPlace.styled.js 파일 보기

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

+ 112
- 15
src/components/MarketPlace/Offers/Offers.js 파일 보기

@@ -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 파일 보기

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

+ 76
- 0
src/components/Paging/Paging.js 파일 보기

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

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

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

export default Paging;

+ 137
- 0
src/components/Paging/Paging.styled.js 파일 보기

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

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

+ 82
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

+ 7
- 2
src/components/Select/Select.styled.js 파일 보기

@@ -9,9 +9,14 @@ export const SelectStyled = styled(Select)`
font-size: 16px;
font-weight: 600;
font-family: ${selectedTheme.fonts.textFont};
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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

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

+ 182
- 0
src/hooks/useQueryString.js 파일 보기

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

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

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

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

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

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

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

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

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

+ 30
- 0
src/hooks/useScreenDimensions.js 파일 보기

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

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

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

+ 21
- 0
src/hooks/useSearch.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

+ 8
- 53
src/pages/HomePage/HomePageMUI.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

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

+ 8
- 0
src/request/chatRequest.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

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

+ 9
- 1
src/request/offersRequest.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

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

+ 10
- 0
src/store/actions/queryString/queryStringActions.js 파일 보기

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

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

+ 5
- 3
src/store/middleware/accessTokensMiddleware.js 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.

Loading…
취소
저장