Procházet zdrojové kódy

Merge branch 'master' of http://git.dilig.net/selenaaasi/trampa-frontend into bugfix/472

feature/587
Djordje Mitrovic před 3 roky
rodič
revize
1b7778aa2f
100 změnil soubory, kde provedl 2937 přidání a 1736 odebrání
  1. 5
    0
      package-lock.json
  2. 1
    0
      package.json
  3. 2
    2
      src/App.js
  4. 46
    45
      src/AppRoutes.js
  5. 1
    0
      src/assets/styles/_base.scss
  6. 23
    0
      src/components/Cards/CreateOfferCard/BackButton/BackButton.js
  7. 16
    0
      src/components/Cards/CreateOfferCard/BackButton/BackButton.styled.js
  8. 22
    0
      src/components/Cards/CreateOfferCard/CloseButton/CloseButton.js
  9. 16
    0
      src/components/Cards/CreateOfferCard/CloseButton/CloseButton.styled.js
  10. 47
    105
      src/components/Cards/CreateOfferCard/CreateOffer.js
  11. 15
    16
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  12. 89
    73
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  13. 41
    45
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  14. 7
    10
      src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js
  15. 7
    7
      src/components/Cards/FilterCard/Choser/CategoryChoser/CategoryChoser.js
  16. 3
    7
      src/components/Cards/FilterCard/Choser/LocationChoser/LocationChoser.js
  17. 21
    21
      src/components/Cards/FilterCard/Choser/SubcategoryChoser/SubcategoryChoser.js
  18. 17
    8
      src/components/Cards/FilterCard/FilterCard.js
  19. 7
    4
      src/components/Cards/FilterCard/FilterCard.styled.js
  20. 1
    0
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  21. 2
    3
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  22. 10
    8
      src/components/Cards/FilterCard/FilterFooter/FilterFooter.js
  23. 4
    4
      src/components/Cards/FilterCard/FilterHeader/FilterHeader.js
  24. 28
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonChooserHeader/SkeletonChooserHeader.js
  25. 33
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonChooserHeader/SkeletonChooserHeader.styled.js
  26. 19
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonChooserTitle/SkeletonChooserTitle.js
  27. 19
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonChooserTitle/SkeletonChooserTitle.styled.js
  28. 39
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonFilterCard.js
  29. 30
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonFilterCard.styled.js
  30. 25
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSection.js
  31. 10
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSection.styled.js
  32. 22
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSectionOption/SkeletonSectionOption.js
  33. 27
    0
      src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSectionOption/SkeletonSectionOption.styled.js
  34. 97
    40
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.js
  35. 111
    4
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js
  36. 12
    5
      src/components/Cards/OfferCard/DeleteOffer/DeleteOffer.js
  37. 5
    5
      src/components/Cards/OfferCard/DeleteOffer/DeleteOffer.styled.js
  38. 2
    1
      src/components/Cards/OfferCard/OfferCard.js
  39. 14
    2
      src/components/Cards/OfferCard/OfferCard.styled.js
  40. 63
    0
      src/components/Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard.js
  41. 133
    0
      src/components/Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled.js
  42. 1
    1
      src/components/ChatColumn/ChatColumn.js
  43. 4
    4
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.js
  44. 16
    4
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js
  45. 47
    51
      src/components/Header/Header.js
  46. 0
    23
      src/components/Header/Header.styled.js
  47. 17
    13
      src/components/Icon/IconWithNumber/IconWithNumber.js
  48. 1
    1
      src/components/ItemDetails/Header/Header.js
  49. 7
    5
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js
  50. 96
    126
      src/components/MarketPlace/Header/Header.js
  51. 1
    1
      src/components/MarketPlace/Header/Header.styled.js
  52. 25
    0
      src/components/MarketPlace/Header/SkeletonHeader/SkeletonHeader.js
  53. 48
    0
      src/components/MarketPlace/Header/SkeletonHeader/SkeletonHeader.styled.js
  54. 21
    2
      src/components/MarketPlace/MarketPlace.js
  55. 4
    3
      src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.js
  56. 10
    1
      src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.styled.js
  57. 58
    27
      src/components/MarketPlace/Offers/Offers.js
  58. 27
    0
      src/components/MarketPlace/Offers/Offers.styled.js
  59. 1
    0
      src/components/Paging/Paging.js
  60. 1
    1
      src/components/Popovers/MyMessages/MyMessages.js
  61. 27
    9
      src/components/Profile/ProfileOffers/ProfileOffers.js
  62. 13
    35
      src/components/ProfileCard/EditProfile/EditProfile.js
  63. 10
    8
      src/components/ProfileCard/EditProfile/EditProfile.styled.js
  64. 20
    131
      src/components/ProfileCard/ProfileCard.js
  65. 188
    143
      src/components/ProfileCard/ProfileCard.styled.js
  66. 48
    0
      src/components/ProfileCard/ProfileContact/ProfileContact.js
  67. 72
    0
      src/components/ProfileCard/ProfileContact/ProfileContact.styled.js
  68. 46
    0
      src/components/ProfileCard/ProfileMainInfo/ProfileMainInfo.js
  69. 79
    0
      src/components/ProfileCard/ProfileMainInfo/ProfileMainInfo.styled.js
  70. 44
    0
      src/components/ProfileCard/ProfileStats/ProfileStats.js
  71. 33
    0
      src/components/ProfileCard/ProfileStats/ProfileStats.styled.js
  72. 3
    3
      src/components/TextFields/TextField/TextField.js
  73. 3
    0
      src/constants/queryStringConstants.js
  74. 0
    177
      src/hooks/useFilters.js
  75. 0
    253
      src/hooks/useOffers.js
  76. 66
    0
      src/hooks/useOffers/useCategoryFilter.js
  77. 50
    0
      src/hooks/useOffers/useFilters.js
  78. 54
    0
      src/hooks/useOffers/useLocationsFilter.js
  79. 101
    0
      src/hooks/useOffers/useMyOffers.js
  80. 144
    0
      src/hooks/useOffers/useOffers.js
  81. 38
    0
      src/hooks/useOffers/usePaging.js
  82. 60
    0
      src/hooks/useOffers/useQueryString.js
  83. 46
    0
      src/hooks/useOffers/useSearch.js
  84. 65
    0
      src/hooks/useOffers/useSorting.js
  85. 56
    0
      src/hooks/useOffers/useSubcategoryFilter.js
  86. 0
    174
      src/hooks/useQueryString.js
  87. 11
    11
      src/hooks/useSearch.js
  88. 36
    0
      src/hooks/useSkeleton.js
  89. 0
    86
      src/hooks/useSorting.js
  90. 16
    3
      src/i18n/resources/rs.js
  91. 4
    4
      src/pages/ErrorPages/NotFoundPage.js
  92. 24
    3
      src/pages/ErrorPages/NotFoundPage.styles.js
  93. 39
    4
      src/pages/HomePage/HomePageMUI.js
  94. 5
    3
      src/pages/ItemDetailsPage/ItemDetailsPageMUI.js
  95. 38
    3
      src/pages/MyOffers/MyOffers.js
  96. 2
    1
      src/store/actions/app/appActionConstants.js
  97. 8
    4
      src/store/actions/app/appActions.js
  98. 4
    2
      src/store/actions/categories/categoriesActionConstants.js
  99. 7
    1
      src/store/actions/categories/categoriesActions.js
  100. 0
    0
      src/store/actions/chat/chatActionConstants.js

+ 5
- 0
package-lock.json Zobrazit soubor

@@ -14544,6 +14544,11 @@
"react-transition-group": "^4.3.0"
}
},
"react-singleton-hook": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/react-singleton-hook/-/react-singleton-hook-3.4.0.tgz",
"integrity": "sha512-eQEpyacGAaRejmWUizUdNNQFn5AO0iaKRSl1jxgC0FQadVY/I1WFuPrYiutglPzO9s8yEbIh95UXVJQel4d7HQ=="
},
"react-toastify": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.3.tgz",

+ 1
- 0
package.json Zobrazit soubor

@@ -34,6 +34,7 @@
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-select": "^4.3.1",
"react-singleton-hook": "^3.4.0",
"react-toastify": "^9.0.3",
"redux": "^4.1.0",
"redux-persist": "^6.0.0",

+ 2
- 2
src/App.js Zobrazit soubor

@@ -83,14 +83,14 @@ const App = () => {
<Header />
<GlobalStyle />
<ToastContainer />
{/* <div>
{/* <div>
<p>Connected: {"" + isConnected}</p>
<br />
<p>Last pong: {lastPong || "-"}</p>
<br />
<button onClick={sendPing}>Send ping</button>
</div> */}
<AppRoutes />
<AppRoutes />
</StyledEngineProvider>
</Router>
);

+ 46
- 45
src/AppRoutes.js Zobrazit soubor

@@ -1,6 +1,6 @@
/* eslint-disable */
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import React from "react";
import { Redirect, Route, Switch } from "react-router-dom";

import {
LOGIN_PAGE,
@@ -18,51 +18,52 @@ import {
PROFILE_PAGE,
CHAT_MESSAGE_PAGE,
CHAT_PAGE,
MY_OFFERS_PAGE
} from './constants/pages';
import LoginPage from './pages/LoginPage/LoginPage';
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 MailSent from './pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent';
import Register from './pages/RegisterPages/Register/Register';
import RegisterSuccessful from './pages/RegisterPages/RegisterSuccessful.js/RegisterSuccessful';
import ResetPasswordPage from './pages/ResetPasswordPage/ResetPasswordPage';
import CreateOffer from './pages/CreateOffer/CreateOffer';
import ItemDetailsPage from './pages/ItemDetailsPage/ItemDetailsPageMUI';
import ProfilePage from './pages/ProfilePage/ProfilePage';
import ChatMessagesPage from './pages/ChatMessages/ChatMessages';
import ChatPage from './pages/Chat/Chat';
import MyOffers from './pages/MyOffers/MyOffers';

MY_OFFERS_PAGE,
} from "./constants/pages";
import LoginPage from "./pages/LoginPage/LoginPage";
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 MailSent from "./pages/ForgotPasswordPage/ForgotPasswordMailSent/MailSent";
import Register from "./pages/RegisterPages/Register/Register";
import RegisterSuccessful from "./pages/RegisterPages/RegisterSuccessful.js/RegisterSuccessful";
import ResetPasswordPage from "./pages/ResetPasswordPage/ResetPasswordPage";
import CreateOffer from "./pages/CreateOffer/CreateOffer";
import ItemDetailsPage from "./pages/ItemDetailsPage/ItemDetailsPageMUI";
import ProfilePage from "./pages/ProfilePage/ProfilePage";
import ChatMessagesPage from "./pages/ChatMessages/ChatMessages";
import ChatPage from "./pages/Chat/Chat";
import MyOffers from "./pages/MyOffers/MyOffers";

const AppRoutes = () => {
return (
<Switch>
<Route exact path={BASE_PAGE} component={HomePage} />
<Route exact path={LOGIN_PAGE} component={LoginPage} />
<Route path={NOT_FOUND_PAGE} component={NotFoundPage} />
<Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} />
<Route path={REGISTER_PAGE} component={Register} />
<Route path={ERROR_PAGE} component={ErrorPage} />
<Route path={FORGOT_PASSWORD_MAIL_SENT} component={MailSent} />
<Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage}/>
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage}/>
<Route path={CREATE_OFFER_PAGE} component={CreateOffer}/>
<Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} />
<Route path={PROFILE_PAGE} component={ProfilePage} />
<Route path={HOME_PAGE} component={(props) => {
return <HomePage key={props.match.params.id} />;
}}/>
<PrivateRoute path={CHAT_MESSAGE_PAGE} component={ChatMessagesPage} />
<PrivateRoute path={CHAT_PAGE} component={ChatPage} />
<PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} />
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>
)};

<Switch>
<Route exact path={BASE_PAGE} component={HomePage} />
<Route exact path={LOGIN_PAGE} component={LoginPage} />
<Route path={NOT_FOUND_PAGE} component={NotFoundPage} />
<Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} />
<Route path={REGISTER_PAGE} component={Register} />
<Route path={ERROR_PAGE} component={ErrorPage} />
<Route path={FORGOT_PASSWORD_MAIL_SENT} component={MailSent} />
<Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} />
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage} />
<Route path={CREATE_OFFER_PAGE} component={CreateOffer} />
<Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} />
<Route path={PROFILE_PAGE} component={ProfilePage} />
<Route
path={HOME_PAGE}
component={(props) => {
return <HomePage key={props.match.params.id} />;
}}
/>
<PrivateRoute path={CHAT_MESSAGE_PAGE} component={ChatMessagesPage} />
<PrivateRoute path={CHAT_PAGE} component={ChatPage} />
<PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} />
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>
);
};

export default AppRoutes;

+ 1
- 0
src/assets/styles/_base.scss Zobrazit soubor

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

* {

+ 23
- 0
src/components/Cards/CreateOfferCard/BackButton/BackButton.js Zobrazit soubor

@@ -0,0 +1,23 @@
import React from "react";
import PropTypes from "prop-types";
import { BackIcon } from "./BackButton.styled";
import { ReactComponent as ArrowBack } from "../../../../assets/images/svg/arrow-back.svg";

const BackButton = (props) => {
const backButtonHandler = () => {
props.setCurrentStep((prevState) => prevState - 1);
};

return (
<BackIcon onClick={backButtonHandler}>
{props.currentStep !== 1 ? <ArrowBack /> : ""}
</BackIcon>
);
};

BackButton.propTypes = {
setCurrentStep: PropTypes.func,
currentStep: PropTypes.bool,
};

export default BackButton;

+ 16
- 0
src/components/Cards/CreateOfferCard/BackButton/BackButton.styled.js Zobrazit soubor

@@ -0,0 +1,16 @@
import styled from "styled-components";
import { Box } from "@mui/system";

export const BackIcon = styled(Box)`
cursor: pointer;
position: absolute;
left: 40px;

@media screen and (max-width: 600px) {
left: 20px;

& svg {
width: 20px;
}
}
`;

+ 22
- 0
src/components/Cards/CreateOfferCard/CloseButton/CloseButton.js Zobrazit soubor

@@ -0,0 +1,22 @@
import React from "react";
import PropTypes from "prop-types";
import { CloseIcon } from "./CloseButton.styled";
import { ReactComponent as CloseButtonIcon } from "../../../../assets/images/svg/close-modal.svg";

const CloseButton = (props) => {
const closeModalHandler = () => {
props.closeCreateOfferModal(false);
};

return (
<CloseIcon onClick={closeModalHandler}>
<CloseButtonIcon />
</CloseIcon>
);
};

CloseButton.propTypes = {
closeCreateOfferModal: PropTypes.func,
};

export default CloseButton;

+ 16
- 0
src/components/Cards/CreateOfferCard/CloseButton/CloseButton.styled.js Zobrazit soubor

@@ -0,0 +1,16 @@
import styled from "styled-components";
import { Box } from "@mui/system";

export const CloseIcon = styled(Box)`
cursor: pointer;
position: absolute;
right: 40px;

@media screen and (max-width: 600px) {
right: 20px;

& svg {
width: 20px;
}
}
`;

+ 47
- 105
src/components/Cards/CreateOfferCard/CreateOffer.js Zobrazit soubor

@@ -1,113 +1,52 @@
/* eslint-disable */
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { NavLink, useHistory } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
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";
import Backdrop from "../../MUI/BackdropComponent";
import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors";
import { LOGIN_USER_LOADING } from "../../../store/actions/login/loginActionConstants";
import { TextField } from "../../TextFields/TextField/TextField";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import Link from "../../Link/Link";
import {
CreateOfferContainer,
CreateOfferTitle,
CreateOfferFormContainer,
RegisterAltText,
RegisterTextContainer,
FieldLabel,
ModalCreateOfferContainer,
ModalBackDrop,
ModalHeader,
BackIcon,
CloseIcon,
} from "./CreateOffer.styled";
import selectedTheme from "../../../themes";
import StepProgress from "../../StepProgress/StepProgress";
import { Label } from "../../CheckBox/Label";
import FirstPartCreateOffer from "./FirstPart/FirstPartCreateOffer";
import SecondPartCreateOffer from "./SecondPart/SecondPartCreateOffer";
import ThirdPartCreateOffer from "./ThirdPart/ThirdPartCreateOffer";
import {
addOffer,
fetchOffers,
fetchOneOffer,
fetchProfileOffers,
} from "../../../store/actions/offers/offersActions";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import { editOneOffer } from "../../../store/actions/offers/offersActions";
import { ReactComponent as ArrowBack } from "../../../assets/images/svg/arrow-back.svg";
import { ReactComponent as CloseButton } from "../../../assets/images/svg/close-modal.svg";
import { useTranslation } from "react-i18next";
import BackdropComponent from "../../MUI/BackdropComponent";
import CloseButton from "./CloseButton/CloseButton";
import BackButton from "./BackButton/BackButton";

const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
const CreateOffer = ({ closeCreateOfferModal, editOffer, offer }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const [informations, setInformations] = useState({});
const [showPassword, setShowPassword] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);
const categories = useSelector((state) => state.categories.categories);
const historyRouter = useHistory();

// When user refreshes page
// useEffect(() => {
// function redirectClient() {
// if (!tokens.RefreshToken && !tokens.JwtToken) {
// return;
// }
// }

// redirectClient();
// }, [history, tokens]);

const isLoading = useSelector(
selectIsLoadingByActionType(LOGIN_USER_LOADING)
);
const { t } = useTranslation();
const userId = useSelector(selectUserId);

const handleApiResponseSuccess = (status) => {
if (editOffer === undefined) {
const userId = historyRouter.location.pathname.slice(
9,
historyRouter.location.pathname.length
);
const handleApiResponseSuccess = () => {
if (editOffer) {
dispatch(fetchOneOffer(offer._id));
dispatch(fetchProfileOffers(userId));
historyRouter.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
},
});
} else {
const userId = offer.userId;
dispatch(fetchOffers({ queryString: "" }));
dispatch(fetchProfileOffers(userId));
dispatch(fetchOneOffer(offer._id));
}
};

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

const handleNext = (values) => {
setInformations({ ...informations, ...values });
setCurrentStep((prevState) => prevState + 1);
};

console.log(informations);

const newImgs =
informations.images &&
informations.images
@@ -119,15 +58,6 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
.replace("data:image/png;base64,", "")
);

let subcategories = [];
for (const element of categories) {
if (element.name === informations.category) {
subcategories = element.subcategories.map((item) => item.name);
}
}

console.log(informations);

const offerData = {
name: informations.nameOfProduct,
description: informations.description,
@@ -160,14 +90,6 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
closeCreateOfferModal(false);
};

const backButtonHandler = () => {
setCurrentStep((prevState) => prevState - 1);
};

const closeModalHandler = () => {
closeCreateOfferModal(false);
};

const goStepBack = (stepNumber) => {
setCurrentStep(stepNumber);
const {
@@ -180,13 +102,21 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
subcategory,
} = informations;
if (stepNumber === 1) {
setInformations({});
setInformations({
category,
condition,
description,
location,
nameOfProduct,
subcategory,
});
}
if (stepNumber === 2) {
setInformations({
category,
condition,
description,
images,
location,
nameOfProduct,
subcategory,
@@ -198,25 +128,26 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
<>
<BackdropComponent
isLoading
handleClose={closeModalHandler}
handleClose={closeCreateOfferModal}
position="fixed"
/>
<ModalCreateOfferContainer currentStep={currentStep}>
<CreateOfferContainer currentStep={currentStep}>
<ModalHeader>
<BackIcon onClick={backButtonHandler}>
{currentStep !== 1 ? <ArrowBack /> : ""}
</BackIcon>
<BackButton
currentStep={currentStep}
setCurrentStep={setCurrentStep}
/>
<CreateOfferTitle component="h1" variant="h5">
{currentStep === 3
? "Pregled"
? `${t("offer.review")}`
: `${
editOffer !== undefined ? "Izmena Objave" : "Nova Objava"
editOffer !== undefined
? `${t("offer.changeOffer")}`
: `${t("offer.newOffer")}`
}`}
</CreateOfferTitle>
<CloseIcon onClick={closeModalHandler}>
<CloseButton />
</CloseIcon>
<CloseButton closeCreateOfferModal={closeCreateOfferModal} />
</ModalHeader>

<StepProgress
@@ -225,10 +156,18 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
functions={[() => goStepBack(1), () => goStepBack(2)]}
/>
{currentStep === 1 && (
<FirstPartCreateOffer handleNext={handleNext} offer={offer} />
<FirstPartCreateOffer
handleNext={handleNext}
offer={offer}
informations={informations}
/>
)}
{currentStep === 2 && (
<SecondPartCreateOffer handleNext={handleNext} offer={offer} />
<SecondPartCreateOffer
handleNext={handleNext}
offer={offer}
informations={informations}
/>
)}
{currentStep === 3 && (
<ThirdPartCreateOffer
@@ -250,5 +189,8 @@ CreateOffer.propTypes = {
pathname: PropTypes.string,
}),
}),
closeCreateOfferModal: PropTypes.func,
editOffer: PropTypes.bool,
offer: PropTypes.object,
};
export default CreateOffer;

+ 15
- 16
src/components/Cards/CreateOfferCard/CreateOffer.styled.js Zobrazit soubor

@@ -7,7 +7,7 @@ import Select from "../../Select/Select";
export const ModalCreateOfferContainer = styled(Box)`
background-color: #fff;
position: fixed;
${props => props.currentStep === 3 && `overflow-y: auto;`}
${(props) => props.currentStep === 3 && `overflow-y: auto;`}
max-height: 90vh;
top: ${(props) =>
props.currentStep === 1 ? "calc(50% - 400px);" : "calc(50% - 350px);"};
@@ -29,8 +29,8 @@ export const ModalCreateOfferContainer = styled(Box)`
scrollbar-color: #ddd;

@media (max-height: 820px) {
top: ${props => props.currentStep === 1 ? 'calc(50% - 340px)' : 'calc(50% - 340px)'};
top: ${(props) =>
props.currentStep === 1 ? "calc(50% - 340px)" : "calc(50% - 340px)"};
}

@media screen and (max-width: 628px) {
@@ -42,7 +42,6 @@ export const ModalCreateOfferContainer = styled(Box)`
left: 0;
padding: 0 30px;
}

`;

export const ModalHeader = styled(Box)`
@@ -65,19 +64,19 @@ export const BackIcon = styled(Box)`
}
`;

export const CloseIcon = styled(Box)`
cursor: pointer;
position: absolute;
right: 40px;
// export const CloseIcon = styled(Box)`
// cursor: pointer;
// position: absolute;
// right: 40px;

@media screen and (max-width: 600px) {
right: 20px;
// @media screen and (max-width: 600px) {
// right: 20px;

& svg {
width: 20px;
}
}
`;
// & svg {
// width: 20px;
// }
// }
// `;

export const CreateOfferContainer = styled(Container)`
margin-top: 0px;
@@ -126,7 +125,7 @@ export const CreateOfferDescription = styled(Typography)`
export const CreateOfferFormContainer = styled(Box)`
width: 335px;
height: 700px;
${props => props.currentStep === 3 && `width: 120%; height: 420px;`}
${(props) => props.currentStep === 3 && `width: 120%; height: 420px;`}
`;
export const RegisterAltText = styled(Typography)`
font-family: "Poppins";

+ 89
- 73
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js Zobrazit soubor

@@ -24,14 +24,31 @@ const FirstPartCreateOffer = (props) => {

const { t } = useTranslation();

useEffect(() => {
if (!props.offer) {
if (Object.keys(props.informations).length !== 0) {
formik.setFieldValue("nameOfProduct", props.informations.nameOfProduct);
formik.setFieldValue("description", props.informations.description);
formik.setFieldValue("location", props.informations.location);
formik.setFieldValue("category", props.informations.category);
formik.setFieldValue("subcategory", props.informations.subcategory);
let scat = categories.filter(
(cat) => cat.name === props.informations.category
);
setSubcat(scat[0].subcategories.map((x) => x.name));
}
} else {
formik.setFieldValue("location", props.offer.location.city);
formik.setFieldValue("category", props.offer.category.name);
formik.setFieldValue("subcategory", props.offer.subcategory);
}
}, [props.offer, props.informations]);

useEffect(() => {
if (props.offer !== undefined) {
let scat = categories.filter(
(cat) => cat.name === props.offer.category.name
);

console.log(categories);
console.log(scat[0].subcategories.map((x) => x.name));
setSubcat(scat[0].subcategories.map((x) => x.name));
}
}, [props.offer]);
@@ -41,15 +58,11 @@ const FirstPartCreateOffer = (props) => {
};
const formik = useFormik({
initialValues: {
nameOfProduct: `${props.offer === undefined ? "" : props.offer.name}`,
description: `${
props.offer === undefined ? "" : props.offer.description
}`,
location: `${props.offer === undefined ? "" : props.offer.location.city}`,
category: `${props.offer === undefined ? "" : props.offer.category.name}`,
subcategory: `${
props.offer === undefined ? "" : props.offer.subcategory
}`,
nameOfProduct: `${!props.offer ? "" : props.offer.name}`,
description: `${!props.offer ? "" : props.offer.description}`,
location: "default",
category: "default",
subcategory: "default",
},
validationSchema: Yup.object().shape({
nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")),
@@ -67,15 +80,12 @@ const FirstPartCreateOffer = (props) => {

const handleSubcategories = (category) => {
const filtered = categories.filter((cat) => cat.name === category);
console.log(filtered[0].subcategories.map((c) => c.name));
setSubcat(filtered[0].subcategories.map((c) => c.name));
};

return (
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
{/* <Backdrop position="absolute" isLoading={isLoading} /> */}

<FieldLabel leftText={t("offer.title")} />
<TitleField
name="nameOfProduct"
@@ -122,68 +132,70 @@ const FirstPartCreateOffer = (props) => {
/>
)}

<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.location.city
}
onChange={(value) => {
formik.setFieldValue("location", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseLocation")}</SelectOption>
{locations.map((loc) => {
return (
<SelectOption key={loc._if} value={loc.city}>
{loc.city}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.category")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.category.name
}
onChange={(value) => {
formik.setFieldValue("category", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseCategory")}</SelectOption>
{categories.map((cat, i) => {
return (
<SelectOption
key={i}
value={cat.name}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</SelectOption>
);
})}
</SelectField>
<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue={formik.values.location}
onChange={(value) => {
formik.setFieldValue("location", value.target.value);
}}
value={formik.values.location}
>
<SelectOption style={{ display: "none" }} value="default">
{t("offer.choseLocation")}
</SelectOption>
{locations.map((loc) => {
return (
<SelectOption key={loc._id} value={loc.city}>
{loc.city}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.subcategory
}
// defaultValue="default"
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value);
}}
>
<SelectOption value="default">{t("offer.choseSubcategory")}</SelectOption>
{subcat &&
subcat.map((sub, i) => {
<FieldLabel leftText={t("offer.category")} />
<SelectField
defaultValue={formik.values.category}
onChange={(value) => {
formik.setFieldValue("category", value.target.value);
}}
value={formik.values.category}
>
<SelectOption style={{ display: "none" }} value="default">
{t("offer.choseCategory")}
</SelectOption>
{categories.map((cat, i) => {
return (
<SelectOption key={i} value={sub}>
{sub}
<SelectOption
key={i}
value={cat.name}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</SelectOption>
);
})}
</SelectField>
</SelectField>

<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue={formik.values.subcategory}
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value);
}}
value={formik.values.subcategory}
>
<SelectOption style={{ display: "none" }} value="default">
{t("offer.choseSubcategory")}
</SelectOption>
{subcat &&
subcat.map((sub, i) => {
return (
<SelectOption key={i} value={sub}>
{sub}
</SelectOption>
);
})}
</SelectField>
</CreateOfferFormContainer>

<NextButton
@@ -201,10 +213,13 @@ const FirstPartCreateOffer = (props) => {
!formik.values?.description ||
formik.values?.category?.length === 0 ||
!formik.values?.category ||
formik.values?.category === "default" ||
formik.values?.subcategory?.length === 0 ||
!formik.values?.subcategory ||
formik.values?.subcategory === "default" ||
formik.values?.location?.length === 0 ||
!formik.values?.location
!formik.values?.location ||
formik.values?.location === "default"
}
>
{t("offer.continue")}
@@ -217,6 +232,7 @@ FirstPartCreateOffer.propTypes = {
children: PropTypes.any,
handleNext: PropTypes.func,
offer: PropTypes.node,
informations: PropTypes.any,
};

export default FirstPartCreateOffer;

+ 41
- 45
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js Zobrazit soubor

@@ -25,19 +25,29 @@ const SecondPartCreateOffer = (props) => {
const [images, setImages] = useState(
Array.apply(null, Array(numberOfImages)).map(() => {})
); // 3 images
const { t } = useTranslation();

useEffect(() => {
// let editedImages = [];
if (!props.offer) {
if (Object.keys(props.informations).length > 6) {
setImages(
props.informations?.images
? [...props.informations.images]
: [...images]
);
formik.setFieldValue(
"condition",
props.informations?.condition
? props.informations.condition
: "default"
);
}
} else {
formik.setFieldValue("condition", props.offer.condition);
}
}, [props.offer, props.informations]);

// if (props.offer !== undefined && props.offer.images.length === 1) {
// editedImages.push(props.offer.images[0]);
// editedImages.push("");
// editedImages.push("");
// } else if (props.offer !== undefined && props.offer.images.length === 2) {
// editedImages.push(props.offer.images[0]);
// editedImages.push(props.offer.images[1]);
// editedImages.push("");
// }
useEffect(() => {
setImages((prevState) => {
let editedImages = [...prevState];
if (props.offer !== undefined && props.offer.images.length === 1) {
@@ -46,7 +56,6 @@ const SecondPartCreateOffer = (props) => {

if (props.offer !== undefined && props.offer.images.length === 2) {
editedImages[0] = props.offer.images[0];

editedImages[1] = props.offer.images[1];
}

@@ -61,7 +70,6 @@ const SecondPartCreateOffer = (props) => {
return [...newState];
});
};
const { t } = useTranslation();

const imagesEmpty = useMemo(() => {
let numOfImagesEmpty = 0;
@@ -70,28 +78,32 @@ const SecondPartCreateOffer = (props) => {
});
return numOfImagesEmpty;
}, [images]);
// for (let i = 0; i < numberOfImages; i++) {
// let item = images[i];
// if (item === null || item === undefined) imagesEmpty++;
// }

const handleSubmit = (values) => {
props.handleNext(values);
};

const conditionSelectEnumArray = Object.values(conditionSelectEnum);
const filteredconditionSelectEnumArray = conditionSelectEnumArray.map(
(item) => item.mainText
);

const formik = useFormik({
initialValues: {
images: images,
condition: `${props.offer === undefined ? "" : props.offer.condition}`,
condition:
props.informations?.condition || props.offer?.condition || "default",
},
validationSchema: Yup.object().shape({}),
validationSchema: Yup.object().shape({
condition: Yup.string()
.required()
.oneOf(filteredconditionSelectEnumArray),
}),
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

console.log("slike", images);

return (
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
@@ -107,25 +119,6 @@ const SecondPartCreateOffer = (props) => {
/>
);
})}
{/* {props.offer === undefined
? images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))
: editedImages.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))} */}
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
@@ -133,14 +126,12 @@ const SecondPartCreateOffer = (props) => {
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.condition
}
onChange={(value) => {
formik.setFieldValue("condition", value.target.value);
}}
value={formik.values.condition}
>
<SelectOption value="default">
<SelectOption style={{ display: "none" }} value="default">
{t("offer.choseCondition")}
</SelectOption>
{Object.keys(conditionSelectEnum).map((key) => {
@@ -163,9 +154,13 @@ const SecondPartCreateOffer = (props) => {
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
onClick={formik.handleSubmit}
// disabled={imagesEmpty === numberOfImages}
disabled={
props.offer === undefined ? imagesEmpty === numberOfImages : false
(props.offer === undefined
? imagesEmpty === numberOfImages
: false) ||
formik.values?.condition?.length === 0 ||
!formik.values?.condition ||
formik.values?.condition === "default"
}
>
{t("offer.continue")}
@@ -178,6 +173,7 @@ SecondPartCreateOffer.propTypes = {
children: PropTypes.node,
handleNext: PropTypes.func,
offer: PropTypes.node,
informations: PropTypes.any,
};

export default SecondPartCreateOffer;

+ 7
- 10
src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js Zobrazit soubor

@@ -1,16 +1,13 @@
import React from "react";
import PropTypes from "prop-types";
import {
// CreateOfferFormContainer,
PreviewCard,
} from "./ThirdPartCreateOffer.styled";
import { PreviewCard } from "./ThirdPartCreateOffer.styled";
import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled";
import selectedTheme from "../../../../themes";
import { CreateOfferFormContainer } from "../CreateOffer.styled";
import { useTranslation } from "react-i18next";

const ThirdPartCreateOffer = (props) => {
const {t} = useTranslation();
const { t } = useTranslation();
const offer = {
offer: {
category: {
@@ -31,7 +28,11 @@ const ThirdPartCreateOffer = (props) => {

return (
<>
<CreateOfferFormContainer currentStep={3} component="form" onSubmit={handleSubmit}>
<CreateOfferFormContainer
currentStep={3}
component="form"
onSubmit={handleSubmit}
>
<PreviewCard
offer={offer}
showBarterButton={false}
@@ -48,10 +49,6 @@ const ThirdPartCreateOffer = (props) => {
textcolor="white"
fullWidth
onClick={handleSubmit}
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
>
{t("offer.publish")}
</NextButton>

+ 7
- 7
src/components/Cards/FilterCard/Choser/CategoryChoser/CategoryChoser.js Zobrazit soubor

@@ -13,27 +13,27 @@ const CategoryChoser = (props) => {
const filters = props.filters;
const { t } = useTranslation();
const handleSelectCategory = (category) => {
filters.setSelectedCategory(category);
filters.clearSelectedSubcategory();
filters.category.setSelectedCategory(category);
filters.subcategory.setSelectedSubcategory({});
};
return (
<FilterRadioDropdown
data={[...filters?.categories]}
data={[...filters?.category.allCategories]}
icon={
filters.selectedCategory?.name ? (
filters.category.selectedCategoryLocally?.name ? (
<CategoryChosenIcon />
) : (
<CategoryIcon />
)
}
title={
filters.selectedCategory?.name
? filters.selectedCategory?.name
filters.category.selectedCategoryLocally?.name
? filters.category.selectedCategoryLocally?.name
: t("filters.categories.title")
}
searchPlaceholder={t("filters.categories.placeholder")}
setSelected={handleSelectCategory}
selected={filters.selectedCategory}
selected={filters.category.selectedCategoryLocally}
firstOption={firstCategoryOption}
/>
);

+ 3
- 7
src/components/Cards/FilterCard/Choser/LocationChoser/LocationChoser.js Zobrazit soubor

@@ -10,15 +10,11 @@ const LocationChoser = (props) => {
return (
<FilterCheckboxDropdown
searchPlaceholder={t("filters.location.placeholder")}
data={[...filters.locations]}
filters={
filters?.selectedLocations?.length > 0
? [...filters.selectedLocations]
: []
}
data={[...filters.locations.allLocations]}
filters={[...filters.locations.selectedLocationsLocally]}
icon={<LocationIcon />}
title={t("filters.location.title")}
setItemsSelected={filters.setSelectedLocations}
setItemsSelected={filters.locations.setSelectedLocations}
/>
);
};

+ 21
- 21
src/components/Cards/FilterCard/Choser/SubcategoryChoser/SubcategoryChoser.js Zobrazit soubor

@@ -2,56 +2,56 @@ import React, { useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";
import { SubcategoryIcon } from "./SubcategoryChoser.styled";
import FilterRadioDropdown from "../../FilterDropdown/Radio/FilterRadioDropdown";
import _ from "lodash";
import { useTranslation } from "react-i18next";

const firstSubcategoryOption = {
label: "SVE PODKATEGORIJE",
value: { _id: 0 },
};
// const firstSubcategoryOption = {
// label: "SVE PODKATEGORIJE",
// value: { _id: 0 },
// };

const SubcategoryChoser = (props) => {
const filters = props.filters;
const { t } = useTranslation();
const [isOpened, setIsOpened] = useState(false);
const [isDisabled, setIsDisabled] = useState(true);

const setInitialOpen = useMemo(() => {
return _.once(() => {
setIsOpened(true);
});
}, []);
const subcategories = useMemo(() => {
return filters.category.getSubcategories(
filters.category.selectedCategoryLocally?.name
);
}, [filters.category.selectedCategoryLocally]);

useEffect(() => {
if (!filters.selectedCategory || filters.selectedCategory?._id === 0) {
if (!filters.category.selectedCategoryLocally || filters.category.selectedCategoryLocally?._id === 0) {
setIsOpened(false);
setIsDisabled(true);
} else {
setIsDisabled(false);
setInitialOpen();
setIsOpened(true);
}
}, [filters.selectedCategory]);
}, [filters.category.selectedCategoryLocally]);

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

console.log(filters);

return (
<FilterRadioDropdown
data={filters.subcategories ? [...filters.subcategories] : []}
data={subcategories}
icon={<SubcategoryIcon />}
title={
filters.selectedSubcategory?.name
? filters.selectedSubcategory?.name
filters.subcategory.selectedSubcategory?.name
? filters.subcategory.selectedSubcategory?.name
: t("filters.subcategories.title")
}
searchPlaceholder={t("filters.subcategories.placeholder")}
setSelected={filters.setSelectedSubcategory}
selected={filters.selectedSubcategory}
setSelected={filters.subcategory.setSelectedSubcategory}
selected={filters.subcategory.selectedSubcategoryLocally}
open={isOpened}
disabled={isDisabled}
handleOpen={handleOpen}
firstOption={firstSubcategoryOption}
firstOption={filters.subcategory.initialOption}
/>
);
};

+ 17
- 8
src/components/Cards/FilterCard/FilterCard.js Zobrazit soubor

@@ -1,26 +1,31 @@
import React from "react";
import PropTypes from "prop-types";
import { ContentContainer, FilterCardContainer } from "./FilterCard.styled";
import useFilters from "../../../hooks/useFilters";
import HeaderBack from "../../ItemDetails/Header/Header";
import FilterHeader from "./FilterHeader/FilterHeader";
import FilterFooter from "./FilterFooter/FilterFooter";
import CategoryChoser from "./Choser/CategoryChoser/CategoryChoser";
import SubcategoryChoser from "./Choser/SubcategoryChoser/SubcategoryChoser";
import LocationChoser from "./Choser/LocationChoser/LocationChoser";
import SkeletonFilterCard from "./Skeleton/SkeletonFilterCard";

const FilterCard = (props) => {
const filters = useFilters(props.myOffers);
const offers = props.offers;
const filters = offers.filters;
return (
<FilterCardContainer
responsiveOpen={props.responsiveOpen}
responsive={props.responsive}
filtersOpened={props.filtersOpened}
myOffers={props.myOffers}
skeleton={props.skeleton}
>
<SkeletonFilterCard
animationStage={props.animationStage}
skeleton={props.skeleton}
/>
{/* Header title for my offers */}
{props.myOffers && <HeaderBack />}

<FilterHeader />
<FilterHeader filters={filters} />

<ContentContainer>
{/* Categories */}
@@ -34,8 +39,8 @@ const FilterCard = (props) => {
</ContentContainer>

<FilterFooter
closeResponsive={props.closeResponsive}
responsiveOpen={props.responsiveOpen}
toggleFilters={props.toggleFilters}
filters={offers}
/>
</FilterCardContainer>
);
@@ -43,11 +48,15 @@ const FilterCard = (props) => {

FilterCard.propTypes = {
children: PropTypes.node,
filters: PropTypes.any,
offers: PropTypes.any,
responsive: PropTypes.bool,
responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func,
myOffers: PropTypes.bool,
skeleton: PropTypes.bool,
animationStage: PropTypes.number,
filtersOpened: PropTypes.bool,
toggleFilters: PropTypes.func,
};

FilterCard.defaultProps = {

+ 7
- 4
src/components/Cards/FilterCard/FilterCard.styled.js Zobrazit soubor

@@ -8,7 +8,7 @@ export const FilterCardContainer = styled(Box)`
border-top-right-radius: 4px;
height: ${(props) =>
props.myOffers ? `calc(100% - 153px)` : `calc(100% - 90px)`};
padding: 36px;
padding: ${props => props.skeleton ? "0" : "36px"};
background-color: white;
width: calc(100% / 12 * 3.5);
left: 0;
@@ -23,17 +23,17 @@ export const FilterCardContainer = styled(Box)`
min-width: 285px !important;
z-index: 9;
margin-top: -24px;
transition: all ease-in-out 0.36s;
transition: all ease-in-out 1s;
transition: padding 0s;

& header {
position: absolute;
top: -73px;
}

@media (max-width: 900px) {
margin-left: -400px;
${(props) =>
props.responsiveOpen
props.filtersOpened
? `
display: "flex";
margin-left: 0;
@@ -45,6 +45,9 @@ export const FilterCardContainer = styled(Box)`
: "display: none"};
transition: all ease-in-out 0.36s;
}
& * {
${props => props.skeleton && 'display: none;'}
}
@media (max-width: 600px) {
margin-top: -14px;
}

+ 1
- 0
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js Zobrazit soubor

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

+ 2
- 3
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js Zobrazit soubor

@@ -51,12 +51,11 @@ const FilterRadioDropdown = (props) => {
setIsOpened((prevState) => !prevState);
if (props.handleOpen) props.handleOpen();
};

return (
<DropdownList
title={props.title}
textcolor={
!props.selected || props.selected?._id === 0
!props.selected || props.selected?._id === 0 || !props.selected?._id
? selectedTheme.primaryText
: selectedTheme.primaryPurple
}
@@ -64,7 +63,7 @@ const FilterRadioDropdown = (props) => {
toggleIconClosed={<DropdownDown />}
toggleIconOpened={<DropdownUp />}
fullWidth
open={ props?.open !== undefined ? props.open : isOpened}
open={props?.open !== undefined ? props.open : isOpened}
disabled={props.disabled}
setIsOpened={handleOpen}
toggleIconStyles={{

+ 10
- 8
src/components/Cards/FilterCard/FilterFooter/FilterFooter.js Zobrazit soubor

@@ -4,22 +4,23 @@ import { FilterFooterContainer } from "./FilterFooter.styled";
import selectedTheme from "../../../../themes";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import { useTranslation } from "react-i18next";
import useFilters from "../../../../hooks/useFilters";
import useScreenDimensions from "../../../../hooks/useScreenDimensions";

const FilterFooter = (props) => {
const { t } = useTranslation();
const filters = useFilters();
const filters = props.filters;
const screenDimensions = useScreenDimensions();
const handleFilters = () => {
filters.applyFilters();
if (props.closeResponsive) props.closeResponsive();
filters.apply();
if (props.toggleFilters) props.toggleFilters();
};
return (
<FilterFooterContainer responsiveOpen={props.responsiveOpen}>
{props.responsiveOpen && (
<FilterFooterContainer responsiveOpen={screenDimensions.width < 600}>
{screenDimensions.width < 600 && (
<PrimaryButton
variant="outlined"
fullWidth
onClick={props.closeResponsive}
onClick={props.toggleFilters}
textcolor={selectedTheme.primaryPurple}
font="Open Sans"
style={{
@@ -52,7 +53,8 @@ const FilterFooter = (props) => {

(FilterFooter.propTypes = {
responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func,
toggleFilters: PropTypes.func,
filters: PropTypes.any,
}),
(FilterFooter.defaultProps = {
responsiveOpen: false,

+ 4
- 4
src/components/Cards/FilterCard/FilterHeader/FilterHeader.js Zobrazit soubor

@@ -2,14 +2,13 @@ import React from "react";
import PropTypes from "prop-types";
import { FilterHeaderContainer, Title } from "./FilterHeader.styled";
import { useTranslation } from "react-i18next";
import useFilters from "../../../../hooks/useFilters";
import Link from "../../../Link/Link";

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

FilterHeader.propTypes = {
children: PropTypes.node,
filters: PropTypes.any,
};

export default FilterHeader;

+ 28
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonChooserHeader/SkeletonChooserHeader.js Zobrazit soubor

@@ -0,0 +1,28 @@
import React from "react";
import PropTypes from "prop-types";
import {
CircleOne,
CircleSecond,
LeftContainer,
Line,
SkeletonChooserContainer,
} from "./SkeletonChooserHeader.styled";

const SkeletonChooserHeader = (props) => {
return (
<SkeletonChooserContainer>
<LeftContainer>
<CircleOne animationStage={props.animationStage}/>
<Line animationStage={props.animationStage}/>
</LeftContainer>
<CircleSecond animationStage={props.animationStage}/>
</SkeletonChooserContainer>
);
};

SkeletonChooserHeader.propTypes = {
children: PropTypes.node,
animationStage: PropTypes.any,
};

export default SkeletonChooserHeader;

+ 33
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonChooserHeader/SkeletonChooserHeader.styled.js Zobrazit soubor

@@ -0,0 +1,33 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { ItemsTransition } from "../../../OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled";

export const SkeletonChooserContainer = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 39px;
margin-bottom: 3px;
`;

export const CircleOne = styled(ItemsTransition)`
width: 24px;
height: 24px;
border-radius: 100% !important;
position: relative;
bottom: 3px;
`;
export const Line = styled(ItemsTransition)`
width: 117px;
height: 18px;
`;
export const CircleSecond = styled(ItemsTransition)`
width: 18px;
height: 18px;
border-radius: 100% !important;
`;
export const LeftContainer = styled(Box)`
display: flex;
flex-direction: row;
gap: 9px;
`;

+ 19
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonChooserTitle/SkeletonChooserTitle.js Zobrazit soubor

@@ -0,0 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { SkeletonChooserTitleContainer, SkeletonChooserTitleLine } from './SkeletonChooserTitle.styled'

const SkeletonChooserTitle = (props) => {
return (
<SkeletonChooserTitleContainer center={props.center} animationStage={props.animationStage} >
<SkeletonChooserTitleLine center={props.center} animationStage={props.animationStage}/>
</SkeletonChooserTitleContainer>
)
}

SkeletonChooserTitle.propTypes = {
children: PropTypes.any,
center: PropTypes.bool,
animationStage: PropTypes.number,
}

export default SkeletonChooserTitle

+ 19
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonChooserTitle/SkeletonChooserTitle.styled.js Zobrazit soubor

@@ -0,0 +1,19 @@
import styled from "styled-components";
import { BackgroundTransition } from "../../../../MarketPlace/Header/SkeletonHeader/SkeletonHeader.styled";
import { ItemsTransition } from "../../../OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled";

export const SkeletonChooserTitleContainer = styled(ItemsTransition)`
margin-top: ${(props) => (props.center ? "44px" : "18px")};
width: 100%;
height: 40px;
padding: 13px 18px;
`;
export const SkeletonChooserTitleLine = styled(BackgroundTransition)`
width: 108px;
height: 14px;
${(props) =>
props.center &&
`
margin: auto;
`}
`;

+ 39
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonFilterCard.js Zobrazit soubor

@@ -0,0 +1,39 @@
import React from "react";
import PropTypes from "prop-types";
import {
SkeletonFilterCardContainer,
SkeletonHeader,
SkeletonHeaderLineOne,
SkeletonHeaderLineSecond,
} from "./SkeletonFilterCard.styled";
import SkeletonChooserHeader from "./SkeletonChooserHeader/SkeletonChooserHeader";
import SkeletonChooserTitle from "./SkeletonChooserTitle/SkeletonChooserTitle";
import SkeletonSection from "./SkeletonSection/SkeletonSection";

const SkeletonFilterCard = (props) => {

return (
<SkeletonFilterCardContainer animationStage={props.animationStage} skeleton={props.skeleton}>
<SkeletonHeader>
<SkeletonHeaderLineOne animationStage={props.animationStage} />
<SkeletonHeaderLineSecond animationStage={props.animationStage} />
</SkeletonHeader>
<SkeletonChooserHeader animationStage={props.animationStage}/>
<SkeletonChooserTitle animationStage={props.animationStage} />
<SkeletonSection numberOfOptions={14} animationStage={props.animationStage} />
<SkeletonChooserHeader animationStage={props.animationStage} />
<SkeletonChooserHeader animationStage={props.animationStage} />
<SkeletonChooserTitle animationStage={props.animationStage} />
<SkeletonSection numberOfOptions={3} animationStage={props.animationStage} />
<SkeletonChooserTitle center animationStage={props.animationStage} />
</SkeletonFilterCardContainer>
);
};

SkeletonFilterCard.propTypes = {
children: PropTypes.any,
animationStage: PropTypes.number,
skeleton: PropTypes.bool,
};

export default SkeletonFilterCard;

+ 30
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonFilterCard.styled.js Zobrazit soubor

@@ -0,0 +1,30 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { BackgroundTransition } from "../../../MarketPlace/Header/SkeletonHeader/SkeletonHeader.styled";
import { ItemsTransition } from "../../OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled";

export const SkeletonFilterCardContainer = styled(BackgroundTransition)`
display: ${props => props.skeleton ? "block" : "none"};
width: 100%;
height: 100%;
padding: 36px;
& * {
display: flex;
border-radius: 4px;
}
`;
export const SkeletonHeader = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
export const SkeletonHeaderLineOne = styled(ItemsTransition)`
width: 90px;
height: 27px;
`;
export const SkeletonHeaderLineSecond = styled(ItemsTransition)`
width: 78px;
height: 14px;
position: relative;
top: 7px;
`;

+ 25
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSection.js Zobrazit soubor

@@ -0,0 +1,25 @@
import React from "react";
import PropTypes from "prop-types";
import { SkeletonSectionContainer } from "./SkeletonSection.styled";
import SkeletonSectionOption from "./SkeletonSectionOption/SkeletonSectionOption";

const SkeletonSection = (props) => {
const arrayForMapping = Array.apply(null, Array(props.numberOfOptions)).map(
() => {}
);
return (
<SkeletonSectionContainer>
{arrayForMapping.map((item, index) => (
<SkeletonSectionOption key={index} animationStage={props.animationStage} />
))}
</SkeletonSectionContainer>
);
};

SkeletonSection.propTypes = {
children: PropTypes.node,
numberOfOptions: PropTypes.number,
animationStage: PropTypes.number,
};

export default SkeletonSection;

+ 10
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSection.styled.js Zobrazit soubor

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

export const SkeletonSectionContainer = styled(Box)`
padding-left: 18px;
display: flex;
flex-direction: column;
gap: 9px;
margin-top: 18px;
`

+ 22
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSectionOption/SkeletonSectionOption.js Zobrazit soubor

@@ -0,0 +1,22 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Circle, EndLine, Line, OptionLeftContainer, SkeletonSectionOptionContainer } from './SkeletonSectionOption.styled'

const SkeletonSectionOption = (props) => {
return (
<SkeletonSectionOptionContainer>
<OptionLeftContainer>
<Circle animationStage={props.animationStage} />
<Line animationStage={props.animationStage} />
</OptionLeftContainer>
<EndLine animationStage={props.animationStage} />
</SkeletonSectionOptionContainer>
)
}

SkeletonSectionOption.propTypes = {
children: PropTypes.any,
animationStage: PropTypes.number,
}

export default SkeletonSectionOption

+ 27
- 0
src/components/Cards/FilterCard/Skeleton/SkeletonSection/SkeletonSectionOption/SkeletonSectionOption.styled.js Zobrazit soubor

@@ -0,0 +1,27 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { ItemsTransition } from "../../../../OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled";

export const SkeletonSectionOptionContainer = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
export const OptionLeftContainer = styled(Box)`
display: flex;
flex-direction: row;
gap: 9px;
`;
export const Circle = styled(ItemsTransition)`
width: 14px;
height: 14px;
border-radius: 100% !important;
`;
export const Line = styled(ItemsTransition)`
width: 86px;
height: 14px;
`;
export const EndLine = styled(ItemsTransition)`
width: 23px;
height: 14px;
`;

+ 97
- 40
src/components/Cards/ItemDetailsCard/ItemDetailsCard.js Zobrazit soubor

@@ -1,15 +1,22 @@
import React, { useEffect, useMemo } from "react";
import React, { useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
ItemDetailsCardContainer,
OfferInfo,
Info,
DateButtonsContainer,
ButtonsContainer,
PostDate,
CategoryIcon,
SubcategoryIcon,
QuantityIcon,
EyeIcon,
EditIconContainer,
EditIcon,
RemoveIconContainer,
RemoveIcon,
EditDeleteButtons,
} from "./ItemDetailsCard.styled";
import selectedTheme from "../../../themes";
import { useDispatch, useSelector } from "react-redux";
@@ -21,14 +28,20 @@ import { formatDateLocale } from "../../../util/helpers/dateHelpers";
import { startChat } from "../../../util/helpers/chatHelper";
import Information from "./Information/Information";
import { useTranslation } from "react-i18next";
import DeleteOffer from "../OfferCard/DeleteOffer/DeleteOffer";
import CreateOffer from "../CreateOfferCard/CreateOffer";
import OfferDetails from "./OfferDetails/OfferDetails";

const ItemDetailsCard = (props) => {
const [showModalRemove, setShowModalRemove] = useState(false);
const [showModalEdit, setShowModalEdit] = useState(false);
const [isMyProfile, setIsMyProfile] = useState(false);
const offer = props.offer;
const chats = useSelector(selectLatestChats);
const userId = useSelector(selectUserId);
const { t } = useTranslation();
const dispatch = useDispatch();
const idProfile = offer?.offer?.userId;

const increaseOfferCounter = useMemo(() => {
return _.once(function (id) {
@@ -42,53 +55,97 @@ const ItemDetailsCard = (props) => {
}
}, [offer]);

useEffect(() => {
if (userId === idProfile) setIsMyProfile(true);
}, [userId, idProfile]);

const date = formatDateLocale(new Date(offer?.offer?._created));

const startExchange = () => {
startChat(chats, offer?.offer, userId);
};
return (
<ItemDetailsCardContainer
sponsored={props.sponsored.toString()}
halfwidth={props.halfwidth ? 1 : 0}
className={props.className}
>
<OfferInfo>
<Info>
<Information
icon={<CategoryIcon />}
value={offer?.offer?.category?.name}
/>
<Information
icon={<SubcategoryIcon />}
value={offer?.offer?.subcategory}
/>
<Information
icon={<QuantityIcon />}
value={offer?.offer?.condition}
/>
<Information icon={<EyeIcon />} value={offer?.offer?.views?.count} />
</Info>
<PostDate>{date}</PostDate>
</OfferInfo>

<OfferDetails
offer={offer}
showExchangeButton={props.showExchangeButton}
showPublishButton={props.showPublishButton}
/>
const closeEditModalHandler = () => {
setShowModalEdit(false);
};

{!props.halfwidth && props.showExchangeButton && (
<CheckButton
variant={props.sponsored ? "contained" : "outlined"}
buttoncolor={selectedTheme.primaryPurple}
textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple}
onClick={startExchange}
>
{t("itemDetailsCard.startExchangeButton")}
</CheckButton>
const closeRemoveModalHandler = () => {
setShowModalRemove(false);
};

return (
<>
<ItemDetailsCardContainer
sponsored={props.sponsored.toString()}
halfwidth={props.halfwidth ? 1 : 0}
className={props.className}
>
<OfferInfo>
<Info>
<Information
icon={<CategoryIcon />}
value={offer?.offer?.category?.name}
/>
<Information
icon={<SubcategoryIcon />}
value={offer?.offer?.subcategory}
/>
<Information
icon={<QuantityIcon />}
value={offer?.offer?.condition}
/>
<Information
icon={<EyeIcon />}
value={offer?.offer?.views?.count}
/>
</Info>
<DateButtonsContainer>
<PostDate>{date}</PostDate>
{isMyProfile && (
<ButtonsContainer>
<EditDeleteButtons>
<EditIconContainer onClick={() => setShowModalEdit(true)}>
<EditIcon />
</EditIconContainer>
<RemoveIconContainer onClick={() => setShowModalRemove(true)}>
<RemoveIcon />
</RemoveIconContainer>
</EditDeleteButtons>
</ButtonsContainer>
)}
</DateButtonsContainer>
</OfferInfo>
<OfferDetails
offer={offer}
showExchangeButton={props.showExchangeButton}
showPublishButton={props.showPublishButton}
/>

{!props.halfwidth && props.showExchangeButton && (
<CheckButton
variant={props.sponsored ? "contained" : "outlined"}
buttoncolor={selectedTheme.primaryPurple}
textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple}
onClick={startExchange}
>
{t("itemDetailsCard.startExchangeButton")}
</CheckButton>
)}
</ItemDetailsCardContainer>
{showModalRemove && (
<DeleteOffer
offer={offer.offer}
closeModalHandler={closeRemoveModalHandler}
/>
)}
{showModalEdit && (
<CreateOffer
editOffer
offer={offer.offer}
closeCreateOfferModal={closeEditModalHandler}
/>
)}
</ItemDetailsCardContainer>
</>
);
};


+ 111
- 4
src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js Zobrazit soubor

@@ -8,6 +8,9 @@ import { ReactComponent as Category } from "../../../assets/images/svg/category.
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";
import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { ReactComponent as Edit } from "../../../assets/images/svg/edit.svg";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg";

export const ItemDetailsCardContainer = styled(Container)`
display: flex;
@@ -37,11 +40,84 @@ export const OfferInfo = styled(Box)`
flex: 2;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin: 18px 0;
@media (max-width: 600px) {
margin: 0;
}
`;
export const ButtonsContainer = styled(Box)`
display: flex;
align-items: center;

@media screen and (max-width: 600px) {
position: absolute;
top: 75px;
right: 143px;
}
`;

export const EditDeleteButtons = styled(Box)`
display: flex;

@media screen and (max-width: 600px) {
position: absolute;
}
`;
export const EditIconContainer = styled(IconButton)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor};
border-radius: 100%;
padding-top: 2px;
text-align: center;
margin-left: 18px;

@media screen and (max-width: 600px) {
width: 32px;
height: 32px;
}
`;
export const EditIcon = styled(Edit)`
@media screen and (max-width: 600px) {
width: 16px;
height: 16px;
position: relative;
top: -4px;
left: -2px;
}
`;

export const RemoveIconContainer = styled(IconButton)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor};
border-radius: 100%;
padding-top: 2px;
text-align: center;
margin-left: 18px;

@media screen and (max-width: 600px) {
width: 32px;
height: 32px;
}
`;

export const RemoveIcon = styled(Remove)`
@media screen and (max-width: 600px) {
width: 16px;
height: 16px;
position: relative;
top: -4px;
left: -2px;
}
`;

export const DateButtonsContainer = styled(Box)`
display: flex;
align-items: center;
`;

export const PostDate = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
@@ -58,6 +134,8 @@ export const PostDate = styled(Typography)`
bottom: 23px;
left: 18px;
width: 70px;
flex-direction: column;
align-items: start;
}
`;
export const Info = styled(Box)`
@@ -112,6 +190,35 @@ export const OfferViews = styled(Box)`
font-size: 12px;
width: 34%;
`;
export const OfferDescriptionTitle = styled(Box)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
line-height: 16px;
@media (max-width: 600px) {
font-size: 9px;
line-height: 13px;
}
`;
export const OfferDescriptionText = styled(Box)`
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryDarkText};
line-height: 22px;
padding-bottom: 20px;
max-width: ${(props) =>
props.showBarterButton ? "calc(100% - 230px)" : "100%"};
@media (max-width: 600px) {
font-size: 14px;
max-width: 100%;
max-height: 100px;
}
/* max-width: calc(100% - 230px); */
/* overflow: hidden; */
/* display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical; */
`;
export const OfferDescription = styled(Box)`
flex: 3;
`;
@@ -162,16 +269,16 @@ export const PublishButtonContainer = styled(Box)`
`;
export const CategoryIcon = styled(Category)`
width: 14px;
`
`;
export const SubcategoryIcon = styled(Subcategory)`
width: 14px;
`
`;
export const QuantityIcon = styled(Quantity)`
width: 22px;
height: 16px;
`
`;

export const EyeIcon = styled(Eye)`
width: 18px;
height: 20px;
`
`;

src/components/Cards/OfferCard/DeleteOffer.js → src/components/Cards/OfferCard/DeleteOffer/DeleteOffer.js Zobrazit soubor

@@ -15,20 +15,22 @@ import {
RemoveIcon,
SaveButton,
CategoryIcon,
} from "./DeleteOffer.styles";
import selectedTheme from "../../../themes";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import BackdropComponent from "../../MUI/BackdropComponent";
} from "./DeleteOffer.styled";
import selectedTheme from "../../../../themes";
import { ReactComponent as Category } from "../../../../assets/images/svg/category.svg";
import BackdropComponent from "../../../MUI/BackdropComponent";
import { useDispatch } from "react-redux";
import {
fetchProfileOffers,
removeOffer,
} from "../../../store/actions/offers/offersActions";
} from "../../../../store/actions/offers/offersActions";
import { useTranslation, Trans } from "react-i18next";
import { useHistory } from "react-router-dom";

const DeleteOffer = (props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const history = useHistory();
const userId = props.offer.userId;
const offerId = props.offer._id;
const closeDeleteModalHandler = () => {
@@ -39,9 +41,14 @@ const DeleteOffer = (props) => {
dispatch(fetchProfileOffers(userId));
};

console.log(history);

const removeOfferHandler = () => {
dispatch(removeOffer({ offerId, handleApiResponseSuccess }));
props.closeModalHandler();
if (history.location.pathname.includes("proizvodi")) {
history.goBack();
}
};

return (

src/components/Cards/OfferCard/DeleteOffer.styles.js → src/components/Cards/OfferCard/DeleteOffer/DeleteOffer.styled.js Zobrazit soubor

@@ -1,11 +1,11 @@
import { Typography } from "@mui/material";
import { Box } from "@mui/system";
import styled from "styled-components";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash-gold.svg";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../../Icon/Icon";
import { ReactComponent as Remove } from "../../../../assets/images/svg/trash-gold.svg";
import selectedTheme from "../../../../themes";
import { IconButton } from "../../../Buttons/IconButton/IconButton";

export const DeleteOfferContainer = styled(Box)`
width: 537px;

+ 2
- 1
src/components/Cards/OfferCard/OfferCard.js Zobrazit soubor

@@ -30,7 +30,7 @@ import {
StarIcon,
StarIconContainer,
} from "./OfferCard.styled";
import DeleteOffer from "./DeleteOffer";
import DeleteOffer from "./DeleteOffer/DeleteOffer";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import selectedTheme from "../../../themes";
@@ -235,6 +235,7 @@ OfferCard.propTypes = {
messageUser: PropTypes.func,
makeReview: PropTypes.func,
dontShowViews: PropTypes.bool,
skeleton: PropTypes.bool,
};
OfferCard.defaultProps = {
halfwidth: false,

+ 14
- 2
src/components/Cards/OfferCard/OfferCard.styled.js Zobrazit soubor

@@ -10,7 +10,7 @@ import { ReactComponent as Edit } from "../../../assets/images/svg/edit.svg";
import { ReactComponent as Star } from "../../../assets/images/svg/star.svg";

export const OfferCardContainer = styled(Container)`
display: flex;
display: ${(props) => (props.skeleton ? "none" : "flex")};
flex-direction: column;
width: ${(props) => (!props.halfwidth ? "100%" : "49%")};
box-sizing: border-box;
@@ -323,6 +323,10 @@ export const EyeIcon = styled(Eye)`
}
`;
export const RemoveIconContainer = styled(MessageIcon)`
display: block;
top: 18px;
right: 18px;

@media screen and (max-width: 600px) {
position: absolute;
display: block;
@@ -333,7 +337,15 @@ export const RemoveIconContainer = styled(MessageIcon)`
export const RemoveIcon = styled(Remove)``;
export const EditIconContainer = styled(MessageIcon)`
display: block;
right: 70px;
top: 18px;
right: 76px;

@media screen and (max-width: 600px) {
position: absolute;
display: block;
right: 20px;
top: 60%;
}
`;
export const EditIcon = styled(Edit)``;
export const StarIconContainer = styled(MessageIcon)`

+ 63
- 0
src/components/Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard.js Zobrazit soubor

@@ -0,0 +1,63 @@
import React from "react";
import PropTypes from "prop-types";
import {
LeftPart,
RightPart,
SkeletonAuthor,
SkeletonColumnContainer,
SkeletonDescription,
SkeletonDescriptionLine,
SkeletonDetail,
SkeletonExchangeButton,
SkeletonExchangeLine,
SkeletonGroup,
SkeletonImage,
SkeletonLocation,
SkeletonMessageButton,
SkeletonOfferCardContainer,
SkeletonRowGroup,
SkeletonTitle,
SpreadLine,
} from "./SkeletonOfferCard.styled";

const SkeletonOfferCard = (props) => {
return (
<SkeletonOfferCardContainer skeleton={props.skeleton} animationStage={props.animationStage}>
<LeftPart animationStage={props.animationStage}>
<SkeletonImage animationStage={props.animationStage} />
<SkeletonColumnContainer animationStage={props.animationStage}>
<SkeletonTitle animationStage={props.animationStage} />
<SkeletonGroup animationStage={props.animationStage}>
<SkeletonAuthor animationStage={props.animationStage} />
<SkeletonLocation animationStage={props.animationStage} />
</SkeletonGroup>
<SkeletonRowGroup animationStage={props.animationStage}>
<SkeletonDetail animationStage={props.animationStage} />
<SkeletonDetail animationStage={props.animationStage} />
<SkeletonDetail animationStage={props.animationStage} />
</SkeletonRowGroup>
</SkeletonColumnContainer>
</LeftPart>
<SpreadLine />
<RightPart animationStage={props.animationStage}>
<SkeletonDescription animationStage={props.animationStage} />
<SkeletonDescriptionLine animationStage={props.animationStage} />
<SkeletonDescriptionLine animationStage={props.animationStage} />
<SkeletonDescriptionLine animationStage={props.animationStage} />
<SkeletonDescriptionLine animationStage={props.animationStage} />
</RightPart>
<SkeletonExchangeButton animationStage={props.animationStage}>
<SkeletonExchangeLine animationStage={props.animationStage} />
</SkeletonExchangeButton>
<SkeletonMessageButton />
</SkeletonOfferCardContainer>
);
};

SkeletonOfferCard.propTypes = {
children: PropTypes.node,
skeleton: PropTypes.bool,
animationStage: PropTypes.number,
};

export default SkeletonOfferCard;

+ 133
- 0
src/components/Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard.styled.js Zobrazit soubor

@@ -0,0 +1,133 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { BackgroundTransition } from "../../../MarketPlace/Header/SkeletonHeader/SkeletonHeader.styled";

export const ItemsTransition = styled(Box)`
transition-duration: 0.4s;
transition-property: background-color;
background-color: ${props => props.animationStage === 1 ? selectedTheme.filterSkeletonItems : selectedTheme.filterSkeletonItemsSecond} !important;
`;

export const SkeletonOfferCardContainer = styled(BackgroundTransition)`
display: flex;
flex-direction: row;
width: 100%;
box-sizing: border-box;
margin: 14px 0;
border-radius: 4px;
${(props) =>
props.sponsored === "true" &&
`border: 1px solid ${selectedTheme.borderSponsoredColor};`}
padding: 16px;
max-width: 2000px;
height: 180px;
position: relative;
& * {
border-radius: 4px;
}
@media (max-width: 550px) {
height: 184px;
padding: 18px;
padding-top: 12px;
${(props) =>
props.vertical &&
`
height: 330px;
width: 180px;
margin: 0 18px;
`}
}
`;
export const LeftPart = styled(Box)`
display: flex;
flex: 1;
flex-direction: row;
margin-right: 40px;
`;
export const SpreadLine = styled(Box)`
height: 108px;
margin-top: auto;
margin-bottom: auto;
opacity: 0.12;
border: 1px solid black;
`;
export const RightPart = styled(Box)`
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
margin-left: 36px;
padding-top: 20px;
`;
export const SkeletonImage = styled(ItemsTransition)`
width: 144px;
height: 144px;
`;
export const SkeletonColumnContainer = styled(Box)`
display: flex;
margin-left: 18px;
justify-content: space-between;
flex: 1;
flex-direction: column;
`;
export const SkeletonTitle = styled(ItemsTransition)`
width: 90px;
height: 27px;
`;
export const SkeletonGroup = styled(Box)`
display: flex;
flex-direction: column;
gap: 4px;
`;
export const SkeletonAuthor = styled(ItemsTransition)`
width: 117px;
height: 18px;
`;
export const SkeletonLocation = styled(ItemsTransition)`
width: 90px;
height: 18px;
`;
export const SkeletonRowGroup = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
export const SkeletonDetail = styled(ItemsTransition)`
width: 72px;
height: 14px;
background-color: ${selectedTheme.filterSkeletonItems};
`;
export const SkeletonDescription = styled(ItemsTransition)`
width: 72px;
height: 14px;
background-color: ${selectedTheme.filterSkeletonItems};
`;
export const SkeletonDescriptionLine = styled(ItemsTransition)`
width: 221px;
height: 18px;
background-color: ${selectedTheme.filterSkeletonItems};
`;
export const SkeletonMessageButton = styled(ItemsTransition)`
width: 40px;
height: 40px;
border-radius: 100%;
background-color: ${selectedTheme.filterSkeletonItems};
top: 18px;
right: 18px;
`;
export const SkeletonExchangeButton = styled(ItemsTransition)`
width: 180px;
height: 48px;
background-color: ${selectedTheme.filterSkeletonItems};
bottom: 18px;
right: 18px;
position: absolute;
padding-top: 17px;
`;
export const SkeletonExchangeLine = styled(BackgroundTransition)`
width: 108px;
height: 14px;
background-color: ${selectedTheme.filterSkeletonBackground};
margin: auto;
`;

+ 1
- 1
src/components/ChatColumn/ChatColumn.js Zobrazit soubor

@@ -10,7 +10,6 @@ import {
TitleSortContainer,
} from "./ChatColumn.styled";
import { sortEnum } from "../../enums/sortEnum";
import useSorting from "../../hooks/useSorting";
import { ReactComponent as Down } from "../../assets/images/svg/down-arrow.svg";
import { IconStyled } from "../Icon/Icon.styled";
import { Grid } from "@mui/material";
@@ -20,6 +19,7 @@ import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { selectLatestChats } from "../../store/selectors/chatSelectors";
import { fetchChats } from "../../store/actions/chat/chatActions";
import useSorting from "../../hooks/useOffers/useSorting";

export const DownArrow = (props) => {
<IconStyled {...props}>

+ 4
- 4
src/components/DirectChat/DirectChatHeader/DirectChatHeader.js Zobrazit soubor

@@ -31,7 +31,7 @@ const DirectChatHeader = (props) => {
if (exchange.buyer?.givenReview) return true;
}
return false;
}, [exchange, userId])
}, [exchange, userId]);

useEffect(() => {
if (showReviewModal) {
@@ -39,17 +39,17 @@ const DirectChatHeader = (props) => {
} else {
document.body.style.overflow = "auto";
}
}, [showReviewModal])
}, [showReviewModal]);

const makeReview = () => {
setShowReviewModal(true);
};
const handleGiveReviewSuccess = () => {
refetchExchange();
}
};
const refetchExchange = () => {
dispatch(fetchExchange(chat.chat.exchangeId));
}
};
return (
<DirectChatHeaderContainer>
{showReviewModal && (

+ 16
- 4
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js Zobrazit soubor

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import PropTypes from "prop-types";
import {
DirectChatNewMessageContainer,
@@ -17,6 +17,7 @@ import { useHistory, useLocation } from "react-router-dom";

const DirectChatNewMessage = (props) => {
const [typedValue, setTypedValue] = useState("");
const [isFocused, setIsFocused] = useState(false);
const dispatch = useDispatch();
const { t } = useTranslation();
const location = useLocation();
@@ -24,8 +25,8 @@ const DirectChatNewMessage = (props) => {
const handleApiResponseSuccess = () => {
props.refreshChat();
};
const handleSend = () => {
console.log(location.state?.offerId);
const handleSend = useCallback(() => {
console.log(typedValue);
if (location.state?.offerId) {
initiateNewChat(typedValue);
} else {
@@ -38,11 +39,20 @@ const DirectChatNewMessage = (props) => {
);
}
setTypedValue("");
};
}, [typedValue]);
const handleMessageSendSuccess = (newChatId) => {
history.replace(`${newChatId}`);
dispatch(fetchChats());
};

useEffect(() => {
const listener = (event) => {
if (event.keyCode === 13) handleSend();
};
if (isFocused) window.addEventListener("keypress", listener);
return () => window.removeEventListener("keypress", listener);
}, [typedValue]);

const initiateNewChat = (typedValue) => {
const offerId = location.state.offerId;
dispatch(
@@ -54,6 +64,8 @@ const DirectChatNewMessage = (props) => {
<NewMessageField
placeholder={t("messages.sendPlaceholder")}
fullWidth
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
italicPlaceholder
value={typedValue}
onChange={(typed) => setTypedValue(typed.target.value)}

+ 47
- 51
src/components/Header/Header.js Zobrazit soubor

@@ -3,8 +3,8 @@ import {
AddOfferButton,
AuthButtonsContainer,
EndIcon,
FilterContainer,
FilterIcon,
// FilterContainer,
// FilterIcon,
HeaderContainer,
LoginButton,
LogoContainer,
@@ -36,10 +36,10 @@ import { useTranslation } from "react-i18next";
import { IconButton } from "../Buttons/IconButton/IconButton";
import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useSearch } from "../../hooks/useSearch";
import { selectProfileName } from "../../store/selectors/profileSelectors";
import { useHistory, useRouteMatch } from "react-router-dom";
import {
BASE_PAGE,
FORGOT_PASSWORD_MAIL_SENT,
FORGOT_PASSWORD_PAGE,
HOME_PAGE,
@@ -48,32 +48,28 @@ import {
REGISTER_SUCCESSFUL_PAGE,
RESET_PASSWORD_PAGE,
} from "../../constants/pages";
import useFilters from "../../hooks/useFilters";
import FilterCard from "../Cards/FilterCard/FilterCard";
import { useQueryString } from "../../hooks/useQueryString";
import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers";
// import { convertQueryStringForFrontend } from "../../util/helpers/queryHelpers";
import { fetchMineProfile } from "../../store/actions/profile/profileActions";
import CreateOffer from "../Cards/CreateOfferCard/CreateOffer";
import { Drawer as HeaderDrawer } from "./Drawer/Drawer";
import useSearch from "../../hooks/useOffers/useSearch";
// import useQueryString from "../../hooks/useOffers/useQueryString";

const Header = (props) => {
const [openFilters, setOpenFilters] = useState(false);
const Header = () => {
// const setOpenFilters = useState(false)[1];
const [showSearchBar, setShowSearchBar] = useState(true);
const [numberOfFilters, setNumberOfFilters] = useState(0);
const [showCreateOfferModal, setShowCreateOfferModal] = useState(false);
const { t } = useTranslation();
const theme = useTheme();
const searchRef = useRef(null);
const matches = useMediaQuery(theme.breakpoints.down("md"));
const user = useSelector(selectUserId);
const search = useSearch();
const search = useSearch(() => {});
const dispatch = useDispatch();
const name = useSelector(selectProfileName);
const history = useHistory();
const routeMatch = useRouteMatch();
const filters = useFilters();
const searchMobileRef = useRef(null);
const queryStringHook = useQueryString();
const [openDrawer, setOpenDrawer] = useState(false);

useEffect(() => {
@@ -87,6 +83,9 @@ const Header = (props) => {
setUserAnchorEl(null);
};
}, []);
useEffect(() => {
searchRef.current.value = search.searchString ?? "";
}, [search.searchString]);
useEffect(() => {
if (history.location.pathname !== "/home") {
setShowSearchBar(false);
@@ -94,25 +93,18 @@ const Header = (props) => {
setShowSearchBar(true);
}
}, [history.location.pathname]);
useEffect(() => {
setNumberOfFilters(filters.calculateFiltersChosen());
}, [
filters.selectedCategory,
filters.selectedLocations,
filters.selectedSubcategory,
]);

useEffect(() => {
if (queryStringHook.loadedFromURL) {
const queryObject = new URLSearchParams(
convertQueryStringFrontend(queryStringHook.queryString)
);
if (queryObject.has("search")) {
searchRef.current.value = queryObject.get("search");
searchMobileRef.current.value = queryObject.get("search");
}
}
});
// useEffect(() => {
// if (queryStringHook.loadedFromURL) {
// const queryObject = new URLSearchParams(
// convertQueryStringForFrontend(queryStringHook.queryString)
// );
// if (queryObject.has("search")) {
// searchRef.current.value = queryObject.get("search");
// searchMobileRef.current.value = queryObject.get("search");
// }
// }
// });

const closeCreateOfferModal = () => {
setShowCreateOfferModal(false);
@@ -137,15 +129,13 @@ const Header = (props) => {

useEffect(() => {
let shouldShowHeader = true;
console.log(props);
if (
location.pathname === LOGIN_PAGE ||
location.pathname === REGISTER_PAGE ||
location.pathname === REGISTER_SUCCESSFUL_PAGE ||
location.pathname === FORGOT_PASSWORD_PAGE ||
location.pathname === FORGOT_PASSWORD_MAIL_SENT ||
location.pathname === RESET_PASSWORD_PAGE ||
location.pathname === "/"
location.pathname === RESET_PASSWORD_PAGE
) {
shouldShowHeader = false;
}
@@ -183,11 +173,22 @@ const Header = (props) => {
searchRef.current.removeEventListener("keyup", listener);
};
const handleSearch = (value) => {
search.searchOffers(value);
};
const toggleFilters = () => {
setOpenFilters((prevState) => !prevState);
if (
history.location.pathname !== HOME_PAGE &&
history.location.pathname !== BASE_PAGE
) {
const newQueryString = new URLSearchParams({ search: value });
history.push({
pathname: HOME_PAGE,
search: newQueryString.toString(),
});
} else {
search.searchOffers(value);
}
};
// const toggleFilters = () => {
// setOpenFilters((prevState) => !prevState);
// };

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
@@ -383,30 +384,25 @@ const Header = (props) => {
fullWidth
shouldShow={showSearchBar}
ref={searchMobileRef}
placeholder={t("header.searchOffers")}
InputProps={{
endAdornment: (
<React.Fragment>
<FilterContainer number={numberOfFilters}>
<FilterIcon onClick={toggleFilters} />
</FilterContainer>
<EndIcon size="36px">
<SearchIcon
onClick={() => handleSearch(searchMobileRef.current.value)}
/>
</EndIcon>
</React.Fragment>
<EndIcon size="36px">
<SearchIcon
onClick={() => handleSearch(searchMobileRef.current.value)}
/>
</EndIcon>
),
}}
placeholder={t("header.searchOffers")}
italicPlaceholder
onFocus={handleFocusSearch}
onBlur={handleBlurSearch}
/>
<FilterCard
{/* <FilterCard
responsive={true}
responsiveOpen={openFilters}
closeResponsive={toggleFilters}
/>
/> */}
{showCreateOfferModal && (
<CreateOffer closeCreateOfferModal={closeCreateOfferModal} />
)}

+ 0
- 23
src/components/Header/Header.styled.js Zobrazit soubor

@@ -3,10 +3,8 @@ import styled from "styled-components";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import { TextField } from "../TextFields/TextField/TextField";
import { ReactComponent as Search } from "../../assets/images/svg/magnifying-glass.svg";
import { ReactComponent as Filter } from "../../assets/images/svg/filter.svg";
import selectedTheme from "../../themes";
import { Icon } from "../Icon/Icon";
import IconWithNumber from "../Icon/IconWithNumber/IconWithNumber";

export const SearchInput = styled(TextField)`
background-color: #f4f4f4;
@@ -195,25 +193,4 @@ export const SearchInputMobile = styled(SearchInput)`
width: 0;
}
`;
export const FilterContainer = styled(IconWithNumber)`
position: relative;
top: 8px;
left: 95px;
cursor: pointer;
background-color: ${selectedTheme.offerBackgroundColor} !important;
& div {
width: 16px;
height: 16px;
background-color: ${selectedTheme.primaryPurple};
position: absolute;
top: -5px;
right: -5px;
line-height: 15px;
text-align: center;
padding-right: 2px;
}
`;
export const FilterIcon = styled(Filter)`
background-color: ${selectedTheme.offerBackgroundColor};
`;
export const HeaderContainer = styled(Box)``;

+ 17
- 13
src/components/Icon/IconWithNumber/IconWithNumber.js Zobrazit soubor

@@ -1,20 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import { IconWithNumberContainer, Number } from './IconWithNumber.styled'
import React from "react";
import PropTypes from "prop-types";
import { IconWithNumberContainer, Number } from "./IconWithNumber.styled";

const IconWithNumber = (props) => {
return (
<IconWithNumberContainer className={props.className}>
{props.children}
{props.number > 0 && <Number>{props.number}</Number>}
<IconWithNumberContainer
className={props.className}
onClick={props.onClick}
>
{props.children}
{props.number > 0 && <Number>{props.number}</Number>}
</IconWithNumberContainer>
)
}
);
};

IconWithNumber.propTypes = {
children: PropTypes.node,
number: PropTypes.number,
className: PropTypes.string,
}
children: PropTypes.node,
number: PropTypes.number,
className: PropTypes.string,
onClick: PropTypes.func,
};

export default IconWithNumber
export default IconWithNumber;

+ 1
- 1
src/components/ItemDetails/Header/Header.js Zobrazit soubor

@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";

const Header = (props) => {
const history = useHistory();
const {t} = useTranslation();
const { t } = useTranslation();

const handleBackButton = () => {
history.goBack();

+ 7
- 5
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js Zobrazit soubor

@@ -29,17 +29,19 @@ const ItemDetailsHeaderCard = (props) => {
history.push(`/profile/${offer?.offer?.userId}`);
};
const messageUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?.offer?._id);
const chatItem = chats.find(
(item) => item.chat.offerId === offer?.offer?._id
);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`)
history.push(`/messages/${chatItem.chat._id}`);
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id
})
offerId: offer?.offer?._id,
});
}
}
}
};
return (
<ItemDetailsHeaderContainer
isMyProfile={props.isMyProfile}

+ 96
- 126
src/components/MarketPlace/Header/Header.js Zobrazit soubor

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React from "react";
import PropTypes from "prop-types";
import {
HeaderAltLocation,
@@ -18,15 +18,11 @@ import { ReactComponent as GridLine } from "../../../assets/images/svg/offer-gri
import { ReactComponent as Down } from "../../../assets/images/svg/down-arrow.svg";
import selectedTheme from "../../../themes";
import { sortEnum } from "../../../enums/sortEnum";
import useFilters from "../../../hooks/useFilters";
import useSorting from "../../../hooks/useSorting";
import { useTranslation } from "react-i18next";
import { Tooltip } from "@mui/material";
import {
ALL_CATEGORIES,
COMMA,
SPREAD,
} from "../../../constants/marketplaceHeaderTitle";
import SkeletonHeader from "./SkeletonHeader/SkeletonHeader";
import { useSelector } from "react-redux";
import { selectHeaderString } from "../../../store/selectors/filtersSelectors";

const DownArrow = (props) => (
<IconStyled {...props}>
@@ -35,137 +31,108 @@ const DownArrow = (props) => (
);

const Header = (props) => {
const filters = useFilters();
const sorting = useSorting();
const { t } = useTranslation();
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [headerString, setHeaderString] = useState(ALL_CATEGORIES);

//Changing shown sort option on select menu
useEffect(() => {
setSortOption(sorting.selectedSortOption);
}, [sorting.selectedSortOption]);

const sorting = props.sorting;
const headerString = useSelector(selectHeaderString);
// Changing header string on refresh or on load
useEffect(() => {
let headerStringLocal = ALL_CATEGORIES;
if (filters.isApplied) {
// Adding category to header string
if (filters.selectedCategory?.name) {
headerStringLocal = filters.selectedCategory.name;
// Adding subcategories to header string
if (filters.selectedSubcategory?.name) {
headerStringLocal += `${SPREAD}${filters.selectedSubcategory.name}`;
}
}
// Adding locations to header string
if (filters.selectedLocations && filters.selectedLocations?.length > 0) {
headerStringLocal += SPREAD;

filters.selectedLocations.forEach((location, index) => {
// Checking if item is last
if (index + 1 === filters.selectedLocations.length) {
headerStringLocal += location.city;
} else {
headerStringLocal += location.city + COMMA;
}
});
}
}
setHeaderString(headerStringLocal);
}, [
filters.isApplied,
filters.selectedCategory,
filters.selectedSubcategory,
filters.selectedLocations,
]);

const handleChangeSelect = (event) => {
let chosenOption;
for (const sortOption in sortEnum) {
if (sortEnum[sortOption].value === event.target.value) {
chosenOption = sortEnum[sortOption];
sorting.changeSorting(chosenOption);
}
}
// let chosenOption;
sorting.changeSorting(event.target.value);
// for (const sortOption in sortEnum) {
// if (sortEnum[sortOption].value === event.target.value) {
// chosenOption = sortEnum[sortOption];
// sorting.changeSorting(chosenOption);
// }
// }
};

return (
<HeaderContainer>
{/* Setting appropriate header title if page is market place or my offers */}
<Tooltip title={headerString}>
{!props.myOffers ? (
headerString === "Sve kategorije" &&
(sorting.selectedSortOption === sortEnum.INITIAL ||
sorting.selectedSortOption === sortEnum.NEW) ? (
<React.Fragment>
<HeaderLocation initial>{headerString}</HeaderLocation>
<HeaderAltLocation>{t("header.newOffers")}</HeaderAltLocation>
</React.Fragment>
<>
<SkeletonHeader
skeleton={props.skeleton}
animationStage={props.animationStage}
/>
<HeaderContainer skeleton={props.skeleton}>
{/* Setting appropriate header title if page is market place or my offers */}
<Tooltip title={headerString}>
{!props.myOffers ? (
headerString === "Sve kategorije" &&
(sorting.selectedSortOption === sortEnum.INITIAL ||
sorting.selectedSortOption === sortEnum.NEW) ? (
<React.Fragment>
<HeaderLocation initial>{headerString}</HeaderLocation>
<HeaderAltLocation>{t("header.newOffers")}</HeaderAltLocation>
</React.Fragment>
) : (
<HeaderLocation>{headerString}</HeaderLocation>
)
) : (
<HeaderLocation>{headerString}</HeaderLocation>
)
) : (
<MySwapsTitle>
<RefreshIcon /> {t("header.myOffers")}
</MySwapsTitle>
)}
</Tooltip>
{/* ^^^^^^ */}
<MySwapsTitle>
<RefreshIcon /> {t("header.myOffers")}
</MySwapsTitle>
)}
</Tooltip>
{/* ^^^^^^ */}

<HeaderOptions>
<HeaderButtons>
{/* Setting display of offer cards to full width */}
<HeaderButton
iconColor={
props.isGrid
? selectedTheme.iconStrokeColor
: selectedTheme.primaryPurple
}
onClick={() => props.setIsGrid(false)}
>
<GridLine />
</HeaderButton>
{/* ^^^^^^ */}
<HeaderOptions>
<HeaderButtons>
{/* Setting display of offer cards to full width */}
<HeaderButton
iconColor={
props.isGrid
? selectedTheme.iconStrokeColor
: selectedTheme.primaryPurple
}
onClick={() => props.setIsGrid(false)}
>
<GridLine />
</HeaderButton>
{/* ^^^^^^ */}

{/* Setting display of offer cards to half width (Grid) */}
<HeaderButton
iconColor={
props.isGrid
? selectedTheme.primaryPurple
: selectedTheme.iconStrokeColor
}
onClick={() => props.setIsGrid(true)}
>
<GridSquare />
</HeaderButton>
{/* ^^^^^^ */}
</HeaderButtons>

{/* Setting display of offer cards to half width (Grid) */}
<HeaderButton
iconColor={
props.isGrid
? selectedTheme.primaryPurple
: selectedTheme.iconStrokeColor
{/* Select option to choose sorting */}
<HeaderSelect
value={
sorting.selectedSortOption?.value
? sorting.selectedSortOption
: "default"
}
onClick={() => props.setIsGrid(true)}
IconComponent={DownArrow}
onChange={handleChangeSelect}
>
<GridSquare />
</HeaderButton>
<SelectOption style={{ display: "none" }} value="default">
Sortiraj po
</SelectOption>
{Object.keys(sortEnum).map((property) => {
if (sortEnum[property].value === 0) return;
return (
<SelectOption
value={sortEnum[property]}
key={sortEnum[property].value}
>
{sortEnum[property].mainText}
</SelectOption>
);
})}
</HeaderSelect>
{/* ^^^^^^ */}
</HeaderButtons>

{/* Select option to choose sorting */}
<HeaderSelect
value={sortOption?.value ? sortOption.value : "default"}
IconComponent={DownArrow}
onChange={handleChangeSelect}
>
<SelectOption style={{ display: "none" }} value="default">
Sortiraj po
</SelectOption>
{Object.keys(sortEnum).map((property) => {
if(sortEnum[property].value === 0) return;
return (
<SelectOption
value={sortEnum[property].value}
key={sortEnum[property].value}
>
{sortEnum[property].mainText}
</SelectOption>
);
})}
</HeaderSelect>
{/* ^^^^^^ */}
</HeaderOptions>
</HeaderContainer>
</HeaderOptions>
</HeaderContainer>
</>
);
};

@@ -176,6 +143,9 @@ Header.propTypes = {
filters: PropTypes.any,
category: PropTypes.string,
myOffers: PropTypes.bool,
skeleton: PropTypes.bool,
animationStage: PropTypes.number,
sorting: PropTypes.any,
};
Header.defaultProps = {
isGrid: false,

+ 1
- 1
src/components/MarketPlace/Header/Header.styled.js Zobrazit soubor

@@ -8,7 +8,7 @@ import { ReactComponent as Refresh } from "../../../assets/images/svg/refresh.sv

export const HeaderContainer = styled(Box)`
margin-top: 20px;
display: flex;
display: ${props => props.skeleton ? "none" : "flex"};
justify-content: space-between;
align-items: center;
`;

+ 25
- 0
src/components/MarketPlace/Header/SkeletonHeader/SkeletonHeader.js Zobrazit soubor

@@ -0,0 +1,25 @@
import React from 'react'
import PropTypes from 'prop-types'
import { CircleGroup, SkeletonHeaderCircle, SkeletonHeaderContainer, SkeletonHeaderLine, SkeletonHeaderRightLine, SkeletonRowGroup } from './SkeletonHeader.styled'

const SkeletonHeader = (props) => {
return (
<SkeletonHeaderContainer skeleton={props.skeleton}>
<SkeletonHeaderLine animationStage={props.animationStage} />
<SkeletonRowGroup>
<CircleGroup>
<SkeletonHeaderCircle animationStage={props.animationStage} />
<SkeletonHeaderCircle animationStage={props.animationStage} />
</CircleGroup>
<SkeletonHeaderRightLine animationStage={props.animationStage} />
</SkeletonRowGroup>
</SkeletonHeaderContainer>
)
}

SkeletonHeader.propTypes = {
skeleton: PropTypes.bool,
animationStage: PropTypes.number,
}

export default SkeletonHeader

+ 48
- 0
src/components/MarketPlace/Header/SkeletonHeader/SkeletonHeader.styled.js Zobrazit soubor

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

export const BackgroundTransition = styled(Box)`
transition-duration: 0.4s;
transition-property: background-color;
background-color: ${props => props.animationStage === 1 ? selectedTheme.filterSkeletonBackground : selectedTheme.filterSkeletonBackgroundSecond} !important;
`;

export const SkeletonHeaderContainer = styled(Box)`
display: ${(props) => (props.skeleton ? "flex" : "none")};
flex-direction: row;
justify-content: space-between;
margin-top: 36px;
`;
export const SkeletonHeaderLine = styled(BackgroundTransition)`
background-color: ${selectedTheme.filterSkeletonBackground};
width: 234px;
height: 18px;
`;
export const SkeletonRowGroup = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
position: relative;
top: -11px;
`;
export const CircleGroup = styled(Box)`
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 18px;
position: relative;
top: -3px;
margin-right: 46px;
`;
export const SkeletonHeaderCircle = styled(BackgroundTransition)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.filterSkeletonBackground};
border-radius: 100%;
`;
export const SkeletonHeaderRightLine = styled(BackgroundTransition)`
width: 209px;
height: 34px;
background-color: ${selectedTheme.filterSkeletonBackground};
`;

+ 21
- 2
src/components/MarketPlace/MarketPlace.js Zobrazit soubor

@@ -6,11 +6,26 @@ import Offers from "./Offers/Offers";

const MarketPlace = (props) => {
const [isGrid, setIsGrid] = useState(false);
const offers = props.offers;

return (
<MarketPlaceContainer>
<Header isGrid={isGrid} setIsGrid={setIsGrid} myOffers={props.myOffers}/>
<Offers isGrid={isGrid} myOffers={props.myOffers} />
<Header
isGrid={isGrid}
setIsGrid={setIsGrid}
myOffers={props.myOffers}
sorting={props.offers.sorting}
skeleton={props.skeleton}
animationStage={props.animationStage}
/>
<Offers
isGrid={isGrid}
myOffers={props.myOffers}
animationStage={props.animationStage}
skeleton={props.skeleton}
offers={offers}
toggleFilters={props.toggleFilters}
/>
</MarketPlaceContainer>
);
};
@@ -18,6 +33,10 @@ const MarketPlace = (props) => {
MarketPlace.propTypes = {
children: PropTypes.node,
myOffers: PropTypes.bool,
animationStage: PropTypes.number,
skeleton: PropTypes.bool,
offers: PropTypes.any,
toggleFilters: PropTypes.func
};

export default MarketPlace;

+ 4
- 3
src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.js Zobrazit soubor

@@ -1,7 +1,6 @@
import React, { useCallback, useRef } from "react";
import PropTypes from "prop-types";
import { TextField } from "../../../TextFields/TextField/TextField";
import { EndIcon, SearchIcon } from "./HeadersMyOffers.styled";
import { EndIcon, SearchIcon, SearchInput } from "./HeadersMyOffers.styled";
import { useTranslation } from "react-i18next";

const HeadersMyOffers = (props) => {
@@ -21,9 +20,10 @@ const HeadersMyOffers = (props) => {
};
const handleSearch = () => {
props.searchMyOffers(searchRef.current.value);
props.handleSearch();
};
return (
<TextField
<SearchInput
fullWidth
InputProps={{
endAdornment: (
@@ -43,6 +43,7 @@ const HeadersMyOffers = (props) => {
HeadersMyOffers.propTypes = {
children: PropTypes.node,
searchMyOffers: PropTypes.func,
handleSearch: PropTypes.func,
};

export default HeadersMyOffers;

+ 10
- 1
src/components/MarketPlace/Offers/HeaderMyOffers.js/HeadersMyOffers.styled.js Zobrazit soubor

@@ -3,7 +3,7 @@ import styled from "styled-components";
import { Icon } from "../../../Icon/Icon";
import { ReactComponent as Search } from "../../../../assets/images/svg/magnifying-glass.svg";
import selectedTheme from "../../../../themes";
import { TextField } from "../../../TextFields/TextField/TextField";

export const HeadersMyOffersContainer = styled(Box)``;
export const EndIcon = styled(Icon)``;
@@ -23,3 +23,12 @@ export const SearchIcon = styled(Search)`
left: 11px;
}
`;
export const SearchInput = styled(TextField)`
& div {
height: 40px;
}
@media (max-width: 600px) {
width: 90%;
height: 36px;
}
`;

+ 58
- 27
src/components/MarketPlace/Offers/Offers.js Zobrazit soubor

@@ -1,53 +1,80 @@
import React, { useRef } from "react";
import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled";
import { FilterContainer, FilterIcon, OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import { useSelector } from "react-redux";
import Paging from "../../Paging/Paging";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import { startChat } from "../../../util/helpers/chatHelper";
import useOffers from "../../../hooks/useOffers";
import OffersNotFound from "./OffersNotFound";
import HeadersMyOffers from "./HeaderMyOffers.js/HeadersMyOffers";
import SkeletonOfferCard from "../../Cards/OfferCard/SkeletonOfferCard/SkeletonOfferCard";

const Offers = (props) => {
const chats = useSelector(selectLatestChats);
const offersRef = useRef(null);
const userId = useSelector(selectUserId);
const offers = useOffers(props.myOffers);
const offers = props.offers;
const arrayForMapping = Array.apply(null, Array(4)).map(() => {});

const messageOneUser = (offer) => {
startChat(chats, offer, userId);
};
const toggleFilters = () => {
props.toggleFilters();
};

console.log(offers.allOffersToShow);
return (
<>
{props.myOffers && (
<HeadersMyOffers searchMyOffers={offers.searchMyOffers} />
)}
{offers.allOffersToShow.length === 0 ? (
<OffersNotFound />
) : (
<OffersContainer ref={offersRef}>
{offers.allOffersToShow.map((item) => {
return (
<OfferCard
key={item._id}
offer={item}
halfwidth={props.isGrid}
messageUser={messageOneUser}
<FilterContainer
onClick={toggleFilters}
number={offers.filters.numOfFiltersChosen}
myOffers={props.myOffers}
>
<FilterIcon />
</FilterContainer>
{!props.skeleton ? (
<>
{props.myOffers && (
<HeadersMyOffers
searchMyOffers={offers.search.searchOffers}
handleSearch={offers.apply}
/>
)}
{offers.allOffersToShow.length === 0 ? (
<OffersNotFound />
) : (
<OffersContainer ref={offersRef}>
{offers.allOffersToShow.map((item) => {
return (
<OfferCard
key={item._id}
offer={item}
halfwidth={props.isGrid}
messageUser={messageOneUser}
/>
);
})}
<Paging
totalElements={offers.totalOffers}
elementsPerPage={10}
current={parseInt(offers.paging.currentPage)}
changePage={offers.paging.changePage}
/>
);
})}
<Paging
totalElements={offers.totalOffers}
elementsPerPage={10}
current={offers.page}
changePage={offers.handleDifferentPage}
/>
</OffersContainer>
</OffersContainer>
)}
</>
) : (
<>
{arrayForMapping.map((item, index) => (
<SkeletonOfferCard
key={index}
skeleton
animationStage={props.animationStage}
/>
))}
</>
)}
</>
);
@@ -57,6 +84,10 @@ Offers.propTypes = {
children: PropTypes.node,
isGrid: PropTypes.bool,
myOffers: PropTypes.bool,
skeleton: PropTypes.bool,
animationStage: PropTypes.number,
offers: PropTypes.any,
toggleFilters: PropTypes.func,
};

Offers.defaultProps = {

+ 27
- 0
src/components/MarketPlace/Offers/Offers.styled.js Zobrazit soubor

@@ -1,5 +1,9 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import IconWithNumber from "../../Icon/IconWithNumber/IconWithNumber";
import { ReactComponent as Filter } from "../../../assets/images/svg/filter.svg";


export const OffersContainer = styled(Box)`
display: flex;
@@ -10,3 +14,26 @@ export const OffersContainer = styled(Box)`
position: relative;
padding-bottom: 60px;
`;
export const FilterContainer = styled(IconWithNumber)`
position: absolute;
top: ${props => props.myOffers ? "126px" : "93px"};
right: 18px;
cursor: pointer;
background-color: ${selectedTheme.offerBackgroundColor} !important;
& div {
width: 16px;
height: 16px;
background-color: ${selectedTheme.primaryPurple};
position: absolute;
top: -5px;
right: -5px;
line-height: 15px;
text-align: center;
}
@media (min-width: 600px) {
display: none;
}
`;
export const FilterIcon = styled(Filter)`
background-color: ${selectedTheme.offerBackgroundColor};
`;

+ 1
- 0
src/components/Paging/Paging.js Zobrazit soubor

@@ -17,6 +17,7 @@ const Paging = (props) => {
: 1;

let moving = 0;
console.log(props.current)
// Making array of pages which contains 2 pages before and after current page
const pagesAsArray = Array.apply(null, Array(5)).map(() => {});


+ 1
- 1
src/components/Popovers/MyMessages/MyMessages.js Zobrazit soubor

@@ -41,7 +41,7 @@ export const MyMessages = () => {
}
}, [chats]);
const goToMessages = () => {
history.push(`messages/${chats[0].chat?._id}`);
history.push(`/messages/${chats[0].chat?._id}`);
};
return (
<HeaderPopover

+ 27
- 9
src/components/Profile/ProfileOffers/ProfileOffers.js Zobrazit soubor

@@ -39,18 +39,20 @@ const ProfileOffers = (props) => {
const userId = useSelector(selectUserId);

const messageUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?.offer?._id);
const chatItem = chats.find(
(item) => item.chat.offerId === offer?.offer?._id
);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`)
history.push(`/messages/${chatItem.chat._id}`);
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id
})
offerId: offer?.offer?._id,
});
}
}
}
};
useEffect(() => {
let newOffersToShow = [...offersToShow];
if (sortOption.value === sortEnum.OLD.value) {
@@ -134,7 +136,9 @@ const ProfileOffers = (props) => {
sx={{ mb: 1.4 }}
>
<OffersIcon />
<HeaderTitle>{props.isMyProfile ? "Moje objave" : "Objave kompanije"}</HeaderTitle>
<HeaderTitle>
{props.isMyProfile ? "Moje objave" : "Objave kompanije"}
</HeaderTitle>
</Grid>
<SearchInput
fullWidth
@@ -155,12 +159,26 @@ const ProfileOffers = (props) => {
<OffersContainer>
{dimensions.width > 600 ? (
offersToShow.map((item) => (
<OfferCard isMyOffer={props.isMyProfile} offer={item} key={JSON.stringify(item)} pinned messageUser={messageUser} />
<OfferCard
isMyOffer={props.isMyProfile}
offer={item}
key={JSON.stringify(item)}
pinned
messageUser={messageUser}
/>
))
) : (
<OffersScroller hideArrows>
{offersToShow.map((item) => (
<OfferCard vertical isMyOffer={props.isMyProfile} offer={item} key={JSON.stringify(item)} pinned messageUser={messageUser} />))}
<OfferCard
vertical
isMyOffer={props.isMyProfile}
offer={item}
key={JSON.stringify(item)}
pinned
messageUser={messageUser}
/>
))}
</OffersScroller>
)}
</OffersContainer>

src/components/ProfileCard/EditProfile.js → src/components/ProfileCard/EditProfile/EditProfile.js Zobrazit soubor

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import BackdropComponent from "../MUI/BackdropComponent";
import BackdropComponent from "../../MUI/BackdropComponent";
import {
EditProfileContainer,
ProfileImageContainer,
@@ -16,19 +16,19 @@ import {
ErrorMessage,
ProfileImagePicker,
} from "./EditProfile.styled";
import selectedTheme from "../../themes";
import selectedTheme from "../../../themes";
import { useFormik } from "formik";
import * as Yup from "yup";
import { ReactComponent as ArrowBack } from "../../assets/images/svg/arrow-back.svg";
import { ReactComponent as CloseIcon } from "../../assets/images/svg/close-modal.svg";
import { ReactComponent as ArrowBack } from "../../../assets/images/svg/arrow-back.svg";
import { ReactComponent as CloseIcon } from "../../../assets/images/svg/close-modal.svg";
import { useTranslation } from "react-i18next";
import {
editMineProfile,
fetchMineProfile,
} from "../../store/actions/profile/profileActions";
import { useDispatch } from "react-redux";
import useScreenDimensions from "../../hooks/useScreenDimensions";
import { useRouteMatch } from "react-router-dom";
} from "../../../store/actions/profile/profileActions";
import { useDispatch, useSelector } from "react-redux";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import useScreenDimensions from "../../../hooks/useScreenDimensions";
import editProfileValidation from "../../../validations/editProfileValidation";

const EditProfile = (props) => {
const [profileImage, setProfileImage] = useState(props.profile.image);
@@ -37,8 +37,7 @@ const EditProfile = (props) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const dimensions = useScreenDimensions();
const routeMatch = useRouteMatch();
const userId = routeMatch.params.idProfile;
const userId = useSelector(selectUserId);

useEffect(() => {
if (dimensions.width < 600) {
@@ -68,18 +67,7 @@ const EditProfile = (props) => {
firmPhone: `${props.profile.company.contacts.telephone}`,
firmLogo: profileImage,
},
validationSchema: Yup.object().shape({
firmName: Yup.string().required(t("editProfile.labelNameRequired")),
firmPIB: Yup.string()
.required(t("editProfile.labelPIBRequired"))
.min(9, t("register.PIBnoOfCharacters")),
firmLocation: Yup.string().required(
t("editProfile.labelLocationRequired")
),
firmWebsite: Yup.string(),
firmApplink: Yup.string(),
firmPhone: Yup.string().required(t("editProfile.labelPhoneRequired")),
}),
validationSchema: editProfileValidation,
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
@@ -129,17 +117,16 @@ const EditProfile = (props) => {
value={formik.values.firmName}
onChange={formik.handleChange}
error={formik.touched.firmName && formik.errors.firmName}
// helperText={formik.touched.firmName && formik.errors.firmName}
margin="normal"
fullWidth
/>
<InputFieldLabel leftText={t("common.labelPIB")} />
<InputField
name="firmPIB"
type="number"
value={formik.values.firmPIB}
onChange={formik.handleChange}
error={formik.touched.firmPIB && formik.errors.firmPIB}
// helperText={formik.touched.firmPIB && formik.errors.firmPIB}
margin="normal"
fullWidth
/>
@@ -151,9 +138,6 @@ const EditProfile = (props) => {
value={formik.values.firmLocation}
onChange={formik.handleChange}
error={formik.touched.firmLocation && formik.errors.firmLocation}
// helperText={
// formik.touched.firmLocation && formik.errors.firmLocation
// }
margin="normal"
fullWidth
/>
@@ -184,13 +168,11 @@ const EditProfile = (props) => {
leftText={t("editProfile.phoneNumber").toUpperCase()}
/>
<InputField
type="number"
name="firmPhone"
value={formik.values.firmPhone}
onChange={formik.handleChange}
error={formik.touched.firmPhone && formik.errors.firmPhone}
// helperText={
// formik.touched.firmPhone && formik.errors.firmPhone
// }
margin="normal"
fullWidth
/>
@@ -225,8 +207,6 @@ const EditProfile = (props) => {
) : (
<ButtonsContainer>
<SaveButton
// type="submit"
// variant="outlined"
height="44px"
width="155px"
buttoncolor={selectedTheme.primaryPurple}
@@ -260,8 +240,6 @@ EditProfile.propTypes = {
closeModalHandler: PropTypes.func,
setImage: PropTypes.func,
reFetchProfile: PropTypes.func,
// error: PropTypes.string,
// errorMessage: PropTypes.string,
};

export default EditProfile;

src/components/ProfileCard/EditProfile.styled.js → src/components/ProfileCard/EditProfile/EditProfile.styled.js Zobrazit soubor

@@ -1,8 +1,8 @@
import styled from "styled-components";
import { Box, TextField, Typography } from "@mui/material";
import ImagePicker from "../ImagePicker/ImagePicker";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../CheckBox/Label";
import ImagePicker from "../../ImagePicker/ImagePicker";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../../CheckBox/Label";

export const EditProfileContainer = styled(Box)`
background-color: #fff;
@@ -12,15 +12,17 @@ export const EditProfileContainer = styled(Box)`
z-index: 150;
padding: 36px 144px;
width: 623px;
max-height: 90vh;
max-height: 95vh;
overflow-y: auto;

@media screen and (max-width: 600px) {
width: 375px;
height: 653px;
height: 100vh;
max-height: 100vh;
min-height: 90vh;
width: 100vw;
top: 0;
left: 0;
padding: 38px 18px;
top: 60px;
left: calc(50% - 187px);
}
`;


+ 20
- 131
src/components/ProfileCard/ProfileCard.js Zobrazit soubor

@@ -3,28 +3,14 @@ import PropTypes from "prop-types";
import {
EditButton,
ProfileCardWrapper,
ProfileName,
ProfilePIB,
ProfileMainInfo,
ProfileContact,
ContactItem,
ProfileStats,
StatsItem,
ProfileCardContainer,
AvatarImage,
ProfileCardHeader,
HeaderTitle,
PocketIcon,
LocationIcon,
MailIcon,
GlobeIcon,
ProfilePIBContainer,
EditIcon,
MessageIcon,
MessageButton,
ProfileInfoContainer,
} from "./ProfileCard.styled";

import { Grid, Stack } from "@mui/material";

import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
import { useRouteMatch } from "react-router-dom";
import { fetchProfile } from "../../store/actions/profile/profileActions";
@@ -34,7 +20,11 @@ import { selectProfile } from "../../store/selectors/profileSelectors";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { useState } from "react";
import { fetchProfileOffers } from "../../store/actions/offers/offersActions";
import EditProfile from "./EditProfile";
import EditProfile from "./EditProfile/EditProfile";
import ProfileMainInfo from "./ProfileMainInfo/ProfileMainInfo";
import ProfileContact from "./ProfileContact/ProfileContact";
import ProfileStats from "./ProfileStats/ProfileStats";
import { useTranslation } from "react-i18next";

const ProfileCard = () => {
const [isMyProfile, setIsMyProfile] = useState(false);
@@ -44,7 +34,8 @@ const ProfileCard = () => {
const profile = useSelector(selectProfile);
const userId = useSelector(selectUserId);
const idProfile = routeMatch.params.idProfile;
console.log(idProfile);
const { t } = useTranslation();

useEffect(() => {
if (idProfile?.length > 0) {
reFetchProfile();
@@ -78,20 +69,13 @@ const ProfileCard = () => {
document.body.style.overflow = "auto";
}

console.log(profile);
return (
<>
<ProfileCardContainer>
<Grid
container
direction="row"
justifyContent="start"
alignItems="center"
sx={{ mb: 1.4 }}
>
<ProfileCardHeader>
<PersonOutlineIcon color="action" sx={{ mr: 0.9 }} />
<HeaderTitle>Moj Profil</HeaderTitle>
</Grid>
<HeaderTitle>{t("profile.myProfile")}</HeaderTitle>
</ProfileCardHeader>
<ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}>
{isMyProfile ? (
<EditButton onClick={() => setEditProfileModal(true)}>
@@ -102,112 +86,17 @@ const ProfileCard = () => {
<MessageIcon />
</MessageButton>
)}
<Grid
container
direction="column"
justifyContent="center"
alignItems="start"
>
{/* Profile Main Info */}
<ProfileMainInfo
container
direction="row"
justifyContent="start"
alignItems="start"
>
<Grid
direction="column"
justifyContent="start"
alignItems="center"
>
<AvatarImage alt="Player.rs" src={profile?.image} />
</Grid>
<Grid
direction="column"
justifyContent="center"
alignItems="start"
sx={{ ml: 2 }}
>
<ProfileName isMyProfile={isMyProfile} variant="h5">
{profile?.company?.name}
</ProfileName>
<ProfilePIBContainer
container
direction="row"
justifyContent="center"
alignItems="center"
>
<PocketIcon />
<ProfilePIB isMyProfile={isMyProfile} variant="subtitle2">
PIB: {profile?.company?.PIB}
</ProfilePIB>
</ProfilePIBContainer>
</Grid>
</ProfileMainInfo>
<ProfileInfoContainer>
{/* Profile Main Info */}
<ProfileMainInfo profile={profile} isMyProfile={isMyProfile} />
{/* Profile Contact */}
<ProfileContact
container
direction={{ xs: "column", sm: "row" }}
justifyContent={{ xs: "center", sm: "start" }}
alignItems={{ xs: "start", sm: "center" }}
>
<Stack direction="row">
<LocationIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.company?.contacts?.location}
</ContactItem>
</Stack>
<Stack direction="row">
<MailIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.email}
</ContactItem>
</Stack>
<Stack direction="row">
<GlobeIcon isMyProfile={isMyProfile} />
<ContactItem isMyProfile={isMyProfile} variant="subtitle2">
{profile?.company?.contacts?.web}
</ContactItem>
</Stack>
</ProfileContact>
<ProfileContact profile={profile} isMyProfile={isMyProfile} />
{/* Profile Stats */}
<ProfileStats
container
direction="row"
justifyContent="start"
alignItems="center"
>
<Grid
container
direction="column"
justifyContent="center"
alignItems="start"
sx={{ width: "fit-content" }}
>
<StatsItem variant="subtitle2">
<b>{profile?.statistics?.publishes?.count}</b> objava
</StatsItem>

<StatsItem variant="subtitle2">
<b>{percentOfSucceededExchanges}%</b> uspešna komunikacija
</StatsItem>
</Grid>
<Grid
container
direction="column"
justifyContent="center"
alignItems="start"
sx={{ width: "fit-content" }}
>
<StatsItem variant="subtitle2">
<b>{profile?.statistics?.views?.count}</b> ukupnih pregleda
</StatsItem>
<StatsItem variant="subtitle2">
<b>{percentOfSucceededExchanges}%</b> korektna saradnja
</StatsItem>
</Grid>
</ProfileStats>
</Grid>
profile={profile}
percentOfSucceededExchanges={percentOfSucceededExchanges}
/>
</ProfileInfoContainer>
</ProfileCardWrapper>
</ProfileCardContainer>
{editProfileModal && (

+ 188
- 143
src/components/ProfileCard/ProfileCard.styled.js Zobrazit soubor

@@ -2,10 +2,10 @@ import styled from "styled-components";
import { Card, Typography, Grid, Box } from "@mui/material";
import selectedTheme from "../../themes";
import { ReactComponent as Edit } from "../../assets/images/svg/edit.svg";
import { ReactComponent as Pocket } from "../../assets/images/svg/pocket.svg";
import { ReactComponent as Globe } from "../../assets/images/svg/globe.svg";
// import { ReactComponent as Pocket } from "../../assets/images/svg/pocket.svg";
// import { ReactComponent as Globe } from "../../assets/images/svg/globe.svg";
import { ReactComponent as Mail } from "../../assets/images/svg/mail.svg";
import { ReactComponent as Location } from "../../assets/images/svg/location.svg";
// import { ReactComponent as Location } from "../../assets/images/svg/location.svg";
// import { PRIMARY_PURPLE_COLOR, PRIMARY_YELLOW_COLOR } from '../../constants/stylesConstants';

export const ProfileCardContainer = styled(Box)`
@@ -52,97 +52,128 @@ export const ProfileCardWrapper = styled(Card)`
position: relative;
`;

export const ProfileName = styled(Typography)`
color: ${(props) =>
props.isMyProfile
? selectedTheme.primaryYellow
: selectedTheme.primaryPurple};
font-weight: 700;
font-size: 24px;
font-family: "Open Sans";
margin-bottom: 5px;
@media (max-width: 600px) {
font-size: 18px;
}
`;
// export const ProfileName = styled(Typography)`
// color: ${(props) =>
// props.isMyProfile
// ? selectedTheme.primaryYellow
// : selectedTheme.primaryPurple};
// font-weight: 700;
// font-size: 24px;
// font-family: "Open Sans";
// margin-bottom: 5px;
// @media (max-width: 600px) {
// font-size: 18px;
// }
// `;

export const ProfilePIB = styled(Typography)`
color: ${(props) =>
props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
margin-top: 0.18rem;
font-family: "Open Sans";
font-size: 16px;
padding-top: 1px;
@media (max-width: 600px) {
font-size: 14px;
}
`;
export const ProfilePIBContainer = styled(Grid)`
position: relative;
left: 5px;
`;
// export const ProfilePIB = styled(Typography)`
// color: ${(props) =>
// props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
// margin-top: 0.18rem;
// font-family: "Open Sans";
// font-size: 16px;
// padding-top: 1px;
// @media (max-width: 600px) {
// font-size: 14px;
// }
// `;
// export const ProfilePIBContainer = styled(Grid)`
// display: flex;
// justify-content: center;
// align-items: center;
// position: relative;
// left: 5px;
// `;

export const ProfileMainInfo = styled(Grid)``;
// export const ProfileMainInfo = styled(Grid)`
// display: flex;
// justify-content: start;
// align-items: start;
// `;

export const ProfileContact = styled(Grid)`
padding-top: 2rem;
padding-bottom: 2rem;
@media (max-width: 600px) {
padding-bottom: 1rem;
}
`;
// export const AvatarImageContainer = styled(Grid)`
// display: flex;
// justify-content: start;
// align-items: center;
// `;

export const ContactItem = styled(Typography)`
margin-right: 2rem;
margin-left: 0.4rem;
color: ${(props) =>
props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
display: unset;
font-family: "Open Sans";
letter-spacing: 0.02em;
font-size: 16px;
position: relative;
bottom: 1px;
@media (max-width: 600px) {
font-size: 14px;
bottom: 4px;
}
`;
// export const ProfileMainInfoGrid = styled(Grid)`
// display: flex;
// flex-direction: column;
// align-items: start;
// margin-left: 16px;
// `;

export const StatsItem = styled(Typography)`
margin-right: 2rem;
display: unset;
margin-left: 1rem;
font-family: "Open Sans";
font-size: 16px;
margin-bottom: 2px;
@media (max-width: 600px) {
font-size: 12px;
}
`;
// export const ProfileContact = styled(Grid)`
// padding-top: 2rem;
// padding-bottom: 2rem;
// @media (max-width: 600px) {
// padding-bottom: 1rem;
// }
// `;

export const ProfileStats = styled(Grid)`
background: ${selectedTheme.primaryDarkTextSecond};
width: calc(100% + 2rem);
padding-top: 1.3rem;
padding-bottom: 1.3rem;
margin-bottom: -1rem;
margin-left: -1rem;
border-radius: 0 0 4px 4px;
`;
export const AvatarImage = styled.img`
min-height: 144px;
min-width: 144px;
width: 144px;
height: 144px;
border-radius: 100%;
@media (max-width: 600px) {
min-height: 90px;
min-width: 90px;
width: 90px;
height: 90px;
}
// export const ContactItem = styled(Typography)`
// margin-right: 2rem;
// margin-left: 0.4rem;
// color: ${(props) =>
// props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
// display: unset;
// font-family: "Open Sans";
// letter-spacing: 0.02em;
// font-size: 16px;
// position: relative;
// bottom: 1px;
// @media (max-width: 600px) {
// font-size: 14px;
// bottom: 4px;
// }
// `;

// export const StatsItem = styled(Typography)`
// margin-right: 2rem;
// display: unset;
// margin-left: 1rem;
// font-family: "Open Sans";
// font-size: 16px;
// margin-bottom: 2px;
// @media (max-width: 600px) {
// font-size: 12px;
// }
// `;

// export const ProfileStats = styled(Grid)`
// display: flex;
// justify-content: start;
// align-items: center;
// background: ${selectedTheme.primaryDarkTextSecond};
// width: calc(100% + 2rem);
// padding-top: 1.3rem;
// padding-bottom: 1.3rem;
// margin-bottom: -1rem;
// margin-left: -1rem;
// border-radius: 0 0 4px 4px;
// `;
// export const AvatarImage = styled.img`
// min-height: 144px;
// min-width: 144px;
// width: 144px;
// height: 144px;
// border-radius: 100%;
// @media (max-width: 600px) {
// min-height: 90px;
// min-width: 90px;
// width: 90px;
// height: 90px;
// }
// `;

export const ProfileCardHeader = styled(Grid)`
display: flex;
justify-content: start;
align-items: center;
margin-bottom: 11px;
`;

export const HeaderTitle = styled(Typography)`
font-size: 16px;
font-family: "Open Sans";
@@ -152,62 +183,62 @@ export const HeaderTitle = styled(Typography)`
font-size: 12px;
}
`;
export const PocketIcon = styled(Pocket)`
width: 22px;
height: 22px;
position: relative;
left: -5px;
top: 2px;
& path {
stroke: #b4b4b4;
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const MailIcon = styled(Mail)`
height: 24px;
width: 24px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const GlobeIcon = styled(Globe)`
height: 22px;
width: 22px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const LocationIcon = styled(Location)`
height: 22px;
width: 22px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
// export const PocketIcon = styled(Pocket)`
// width: 22px;
// height: 22px;
// position: relative;
// left: -5px;
// top: 2px;
// & path {
// stroke: #b4b4b4;
// }
// @media (max-width: 600px) {
// width: 14px;
// height: 14px;
// }
// `;
// export const MailIcon = styled(Mail)`
// height: 24px;
// width: 24px;
// & path {
// stroke: ${(props) =>
// props.isMyProfile
// ? selectedTheme.iconMineProfileColor
// : selectedTheme.iconProfileColor};
// }
// @media (max-width: 600px) {
// width: 14px;
// height: 14px;
// }
// `;
// export const GlobeIcon = styled(Globe)`
// height: 22px;
// width: 22px;
// & path {
// stroke: ${(props) =>
// props.isMyProfile
// ? selectedTheme.iconMineProfileColor
// : selectedTheme.iconProfileColor};
// }
// @media (max-width: 600px) {
// width: 14px;
// height: 14px;
// }
// `;
// export const LocationIcon = styled(Location)`
// height: 22px;
// width: 22px;
// & path {
// stroke: ${(props) =>
// props.isMyProfile
// ? selectedTheme.iconMineProfileColor
// : selectedTheme.iconProfileColor};
// }
// @media (max-width: 600px) {
// width: 14px;
// height: 14px;
// }
// `;
export const MessageIcon = styled(Mail)`
width: 19.5px;
height: 19.5px;
@@ -223,3 +254,17 @@ export const MessageIcon = styled(Mail)`
right: 0.5px;
}
`;

export const ProfileInfoContainer = styled(Grid)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
`;

// export const ProfileStatsGrid = styled(Grid)`
// display: flex;
// flex-direction: column;
// justify-content: center;
// align-items: start;
// `;

+ 48
- 0
src/components/ProfileCard/ProfileContact/ProfileContact.js Zobrazit soubor

@@ -0,0 +1,48 @@
import React from "react";
import PropTypes from "prop-types";
import {
ProfileContactContainer,
LocationIcon,
ContactItem,
MailIcon,
GlobeIcon,
} from "./ProfileContact.styled";
import { Stack } from "@mui/material";

const ProfileContact = (props) => {
return (
<ProfileContactContainer
container
direction={{ xs: "column", sm: "row" }}
justifyContent={{ xs: "center", sm: "start" }}
alignItems={{ xs: "start", sm: "center" }}
>
<Stack direction="row">
<LocationIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
{props.profile?.company?.contacts?.location}
</ContactItem>
</Stack>
<Stack direction="row">
<MailIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
{props.profile?.email}
</ContactItem>
</Stack>
<Stack direction="row">
<GlobeIcon isMyProfile={props.isMyProfile} />
<ContactItem isMyProfile={props.isMyProfile} variant="subtitle2">
{props.profile?.company?.contacts?.web}
</ContactItem>
</Stack>
</ProfileContactContainer>
);
};

ProfileContact.propTypes = {
profile: PropTypes.object,
isMyProfile: PropTypes.bool,
children: PropTypes.node,
};

export default ProfileContact;

+ 72
- 0
src/components/ProfileCard/ProfileContact/ProfileContact.styled.js Zobrazit soubor

@@ -0,0 +1,72 @@
import styled from "styled-components";
import { Grid, Typography } from "@mui/material";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";
import { ReactComponent as Mail } from "../../../assets/images/svg/mail.svg";
import { ReactComponent as Globe } from "../../../assets/images/svg/globe.svg";
import selectedTheme from "../../../themes";

export const ProfileContactContainer = styled(Grid)`
padding-top: 2rem;
padding-bottom: 2rem;
@media (max-width: 600px) {
padding-bottom: 1rem;
}
`;
export const LocationIcon = styled(Location)`
height: 22px;
width: 22px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const ContactItem = styled(Typography)`
margin-right: 2rem;
margin-left: 0.4rem;
color: ${(props) =>
props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
display: unset;
font-family: "Open Sans";
letter-spacing: 0.02em;
font-size: 16px;
position: relative;
bottom: 1px;
@media (max-width: 600px) {
font-size: 14px;
bottom: 4px;
}
`;
export const MailIcon = styled(Mail)`
height: 24px;
width: 24px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const GlobeIcon = styled(Globe)`
height: 22px;
width: 22px;
& path {
stroke: ${(props) =>
props.isMyProfile
? selectedTheme.iconMineProfileColor
: selectedTheme.iconProfileColor};
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;

+ 46
- 0
src/components/ProfileCard/ProfileMainInfo/ProfileMainInfo.js Zobrazit soubor

@@ -0,0 +1,46 @@
import React from "react";
import PropTypes from "prop-types";
import {
ProfileMainInfoContainer,
AvatarImageContainer,
AvatarImage,
ProfileMainInfoGrid,
ProfileName,
ProfilePIBContainer,
PocketIcon,
ProfilePIB,
} from "./ProfileMainInfo.styled";
import { useTranslation } from "react-i18next";

const ProfileMainInfo = (props) => {
const { t } = useTranslation();
return (
<ProfileMainInfoContainer>
<AvatarImageContainer>
<AvatarImage
alt={props.profile?.company?.name}
src={props.profile?.image}
/>
</AvatarImageContainer>
<ProfileMainInfoGrid>
<ProfileName isMyProfile={props.isMyProfile} variant="h5">
{props.profile?.company?.name}
</ProfileName>
<ProfilePIBContainer>
<PocketIcon />
<ProfilePIB isMyProfile={props.isMyProfile} variant="subtitle2">
{t("profile.PIB")} {props.profile?.company?.PIB}
</ProfilePIB>
</ProfilePIBContainer>
</ProfileMainInfoGrid>
</ProfileMainInfoContainer>
);
};

ProfileMainInfo.propTypes = {
profile: PropTypes.object,
isMyProfile: PropTypes.bool,
children: PropTypes.node,
};

export default ProfileMainInfo;

+ 79
- 0
src/components/ProfileCard/ProfileMainInfo/ProfileMainInfo.styled.js Zobrazit soubor

@@ -0,0 +1,79 @@
import styled from "styled-components";
import { Grid, Typography } from "@mui/material";
import selectedTheme from "../../../themes";
import { ReactComponent as Pocket } from "../../../assets/images/svg/pocket.svg";

export const ProfileMainInfoContainer = styled(Grid)`
display: flex;
justify-content: start;
align-items: start;
`;
export const AvatarImageContainer = styled(Grid)`
display: flex;
justify-content: start;
align-items: center;
`;
export const AvatarImage = styled.img`
min-height: 144px;
min-width: 144px;
width: 144px;
height: 144px;
border-radius: 100%;
@media (max-width: 600px) {
min-height: 90px;
min-width: 90px;
width: 90px;
height: 90px;
}
`;
export const ProfileMainInfoGrid = styled(Grid)`
display: flex;
flex-direction: column;
align-items: start;
margin-left: 16px;
`;
export const ProfileName = styled(Typography)`
color: ${(props) =>
props.isMyProfile
? selectedTheme.primaryYellow
: selectedTheme.primaryPurple};
font-weight: 700;
font-size: 24px;
font-family: "Open Sans";
margin-bottom: 5px;
@media (max-width: 600px) {
font-size: 18px;
}
`;
export const ProfilePIBContainer = styled(Grid)`
display: flex;
justify-content: center;
align-items: center;
position: relative;
left: 5px;
`;
export const PocketIcon = styled(Pocket)`
width: 22px;
height: 22px;
position: relative;
left: -5px;
top: 2px;
& path {
stroke: #b4b4b4;
}
@media (max-width: 600px) {
width: 14px;
height: 14px;
}
`;
export const ProfilePIB = styled(Typography)`
color: ${(props) =>
props.isMyProfile ? "white" : selectedTheme.primaryDarkText};
margin-top: 0.18rem;
font-family: "Open Sans";
font-size: 16px;
padding-top: 1px;
@media (max-width: 600px) {
font-size: 14px;
}
`;

+ 44
- 0
src/components/ProfileCard/ProfileStats/ProfileStats.js Zobrazit soubor

@@ -0,0 +1,44 @@
import React from "react";
import PropTypes from "prop-types";
import {
ProfileStatsContainer,
ProfileStatsGrid,
StatsItem,
} from "./ProfileStats.styled";
import { useTranslation } from "react-i18next";

const ProfileStats = (props) => {
const { t } = useTranslation();
return (
<ProfileStatsContainer>
<ProfileStatsGrid>
<StatsItem variant="subtitle2">
<b>{props.profile?.statistics?.publishes?.count}</b>
{t("profile.publishes")}
</StatsItem>

<StatsItem variant="subtitle2">
<b>{props.percentOfSucceededExchanges}%</b>
{t("profile.successExchange")}
</StatsItem>
</ProfileStatsGrid>
<ProfileStatsGrid>
<StatsItem variant="subtitle2">
<b>{props.profile?.statistics?.views?.count}</b>
{t("profile.numberOfViews")}
</StatsItem>
<StatsItem variant="subtitle2">
<b>{props.percentOfSucceededExchanges}%</b>
{t("profile.successComunication")}
</StatsItem>
</ProfileStatsGrid>
</ProfileStatsContainer>
);
};

ProfileStats.propTypes = {
profile: PropTypes.object,
percentOfSucceededExchanges: PropTypes.number,
};

export default ProfileStats;

+ 33
- 0
src/components/ProfileCard/ProfileStats/ProfileStats.styled.js Zobrazit soubor

@@ -0,0 +1,33 @@
import styled from "styled-components";
import { Grid, Typography } from "@mui/material";
import selectedTheme from "../../../themes";

export const ProfileStatsContainer = styled(Grid)`
display: flex;
justify-content: start;
align-items: center;
background: ${selectedTheme.primaryDarkTextSecond};
width: calc(100% + 2rem);
padding-top: 1.3rem;
padding-bottom: 1.3rem;
margin-bottom: -1rem;
margin-left: -1rem;
border-radius: 0 0 4px 4px;
`;
export const ProfileStatsGrid = styled(Grid)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
`;
export const StatsItem = styled(Typography)`
margin-right: 2rem;
display: unset;
margin-left: 1rem;
font-family: "Open Sans";
font-size: 16px;
margin-bottom: 2px;
@media (max-width: 600px) {
font-size: 12px;
}
`;

+ 3
- 3
src/components/TextFields/TextField/TextField.js Zobrazit soubor

@@ -42,9 +42,9 @@ export const TextField = React.forwardRef((props, ref) => {
label={props.showAnimation ? props.placeholder : ""}
onFocus={props.onFocus}
onBlur={props.onBlur}
italicplaceholder={(props.italicPlaceholder && isFieldEmpty) ? "true" : "false"}
italicplaceholder={
props.italicPlaceholder && isFieldEmpty ? "true" : "false"
}
focused={props.focused}
>
{props.children}

+ 3
- 0
src/constants/queryStringConstants.js Zobrazit soubor

@@ -6,6 +6,9 @@ export const KEY_SORTBY = "sortBy";
export const KEY_SORT_DATE = "_des_date";
export const KEY_SORT_POPULAR = "_des_popular";
export const KEY_LOCATION = "location"
export const KEY_NAME = "name";
export const KEY_SEARCH = "search"
export const VALUE_SORTBY_NEW = "newest";
export const VALUE_SORTBY_OLD = "oldest";
export const VALUE_SORTBY_POPULAR = "popular";
export const initialSize = "10";

+ 0
- 177
src/hooks/useFilters.js Zobrazit soubor

@@ -1,177 +0,0 @@
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { fetchCategories } from "../store/actions/categories/categoriesActions";
import {
setFilteredCategory,
setFilteredLocations,
setFilteredSubcategory,
setIsAppliedStatus,
} from "../store/actions/filters/filtersActions";
import { fetchLocations } from "../store/actions/locations/locationsActions";
import {
selectCategories,
selectSubcategories,
} from "../store/selectors/categoriesSelectors";
import {
selectAppliedStatus,
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSubcategory,
} from "../store/selectors/filtersSelectors";
import { selectLocations } from "../store/selectors/locationsSelectors";
import { useQueryString } from "./useQueryString";

const useFilters = (myOffers) => {
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
const [loadedFromQS, setLoadedFromQS] = useState(false);
const [loaded, setLoadedStatus] = useState(false);
const isApplied = useSelector(selectAppliedStatus);
const categories = useSelector(selectCategories);
const subcategories = useSelector(
selectSubcategories(selectedCategory?.name)
);
const locations = useSelector(selectLocations);
const dispatch = useDispatch();
const queryStringHook = useQueryString();
useEffect(() => {
if (!loaded) {
dispatch(fetchCategories());
dispatch(fetchLocations());
setLoadedStatus(true);
}
}, [categories, locations]);

useEffect(() => {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (categories?.length > 0 && locations?.length > 0) {
let category;
if (queryObject.has("category")) {
category = categories.find(
(item) => item.name === queryObject.get("category").toString()
);
setSelectedCategory(category);
} else {
if (!myOffers) {
setSelectedCategory();
}
}
if (queryObject.has("subcategory")) {
setSelectedSubcategory(
category?.subcategories?.find(
(item) =>
item.name.toString() === queryObject.get("subcategory").toString()
)
);
} else {
if (!myOffers) {
setSelectedSubcategory();
}
}
}
if (queryObject.has("location")) {
let locationsToPush = [];
queryObject.getAll("location").forEach((item) => {
locationsToPush.push(locations.find((p) => p.city === item));
});
setSelectedLocations([...locationsToPush]);
} else {
if (!myOffers) {
setSelectedLocations([]);
}
}
dispatch(setIsAppliedStatus(true));
}, [queryStringHook.queryString, categories, locations]);

// Apply everything
const applyFilters = () => {
makeQueryString();
};

// Clear function
const clearFilters = () => {
setSelectedLocations([]);
setSelectedSubcategory();
setSelectedCategory();
applyFilters();
};

// Helper function
const makeQueryString = () => {
let qsArray = [];
qsArray.push({ key: "category", value: selectedCategory?.name });
qsArray.push({ key: "subcategory", value: selectedSubcategory?.name });
selectedLocations?.forEach((location) => {
qsArray.push({ key: "location", value: location?.city });
});
qsArray.push({ key: "page", value: "1" });
queryStringHook.appendMultipleToQueryString(qsArray);
};

//Calculate chosen categories for number above filter icon on mobile responsive version
const calculateFiltersChosen = () => {
let sum = 0;
if (selectedCategory && selectedCategory?._id !== 0) {
sum++;
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
sum++;
}
if (selectedLocations && selectedLocations?.length > 0) {
sum += selectedLocations.length;
}
return sum;
};

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

export default useFilters;

+ 0
- 253
src/hooks/useOffers.js Zobrazit soubor

@@ -1,253 +0,0 @@
import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../constants/pages";
import { KEY_PAGE, KEY_SIZE } from "../constants/queryStringConstants";
import { sortEnum } from "../enums/sortEnum";
import { fetchChats } from "../store/actions/chat/chatActions";
import {
fetchMineOffers,
fetchOffers,
} from "../store/actions/offers/offersActions";
import {
selectAppliedStatus,
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSortOption,
selectSelectedSubcategory,
} from "../store/selectors/filtersSelectors";
import {
selectMineOffers,
selectOffers,
selectPinnedOffers,
selectTotalOffers,
} from "../store/selectors/offersSelectors";
import { useQueryString } from "./useQueryString";

const useOffers = (myOffers) => {
const history = useHistory();
const pinnedOffers = useSelector(selectPinnedOffers);
const offers = useSelector(selectOffers);
const mineOffers = useSelector(selectMineOffers);
const dispatch = useDispatch();
const queryStringHook = useQueryString();
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
const selectedSortOption = useSelector(selectSelectedSortOption);
const isApplied = useSelector(selectAppliedStatus);
const total = useSelector(selectTotalOffers);
const [page, setPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [myOffersLength, setMyOffersLength] = useState(0);

//Fetching chats
useEffect(() => {
dispatch(fetchChats());
}, []);

//Setting appropriate page based on query string
useEffect(() => {
let queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has(KEY_PAGE) && queryObject.get(KEY_PAGE) !== 1) {
setPage(parseInt(queryObject.get(KEY_PAGE)));
}
}, [history.location.search]);

// Checking if page is opened by clicking on logo on header, and fetching offers
// with empty query string if previous statement is true
useEffect(() => {
if (history?.location?.state?.logo || history?.location?.state?.refetch) {
dispatch(fetchOffers({ queryString: "" }));
queryStringHook.setQueryString("");
setPage(1);
history.location.state = undefined;
}
}, [history.location.state]);

// Initialy loading offers with filters from query string
useEffect(() => {
if (queryStringHook.loadedFromURL) {
refetch();
} else {
queryStringHook.appendMultipleToQueryString([
{ key: KEY_SIZE, value: "10" },
{ key: KEY_PAGE, value: "1" },
]);
}
}, [queryStringHook.loadedFromURL, queryStringHook.queryString]);

// Changing offers when page changes
useEffect(() => {
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has(KEY_PAGE)) {
if (queryObject.get(KEY_PAGE) !== page.toString()) {
queryStringHook.appendToQueryString(KEY_PAGE, page);
} else {
refetch();
}
} else {
queryStringHook.appendToQueryString(KEY_PAGE, page);
}
}, [page]);

// All pinned to show when market place is opened
const pinnedOffersToShow = useMemo(() => {
if (myOffers) {
return mineOffers.filter((item) => item.pinned);
}
return pinnedOffers;
}, [pinnedOffers, mineOffers, page, myOffers]);

// Normal offers to show when market place is opened
const offersToShow = useMemo(() => {
if (myOffers) {
return mineOffers.filter((item) => item.pinned === false);
}
return offers;
}, [offers, mineOffers, page, myOffers]);

// Offers to show when market place is opened and when my offers are opened
const allOffersToShow = useMemo(() => {
let newOffers = [...pinnedOffersToShow, ...offersToShow];
if (myOffers) {
// Filtering my offers based on category
if (selectedCategory && selectedCategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.category.name === selectedCategory.name
);
}
// Filtering my offers based on subcategory
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.subcategory === selectedSubcategory.name
);
}
// Filtering my offers based on locations
if (selectedLocations && selectedLocations?.length > 0) {
newOffers = newOffers.filter((item) => {
let isInOneOfLocations = false;
selectedLocations?.forEach((location) => {
if (item.location.city === location.city) {
isInOneOfLocations = true;
}
});
return isInOneOfLocations;
});
}
// Sorting my offers based on chosen sorting option
// Old offers are arrays used for sorting
let oldOffers = [...offersToShow];
let oldPinnedOffers = [...pinnedOffersToShow];
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.NEW.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.OLD.value
) {
newOffers = newOffers.sort(
(itemA, itemB) => new Date(itemA._created) - new Date(itemB._created)
);
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.POPULAR.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
...oldOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
];
}
newOffers = newOffers.filter((item) =>
item?.name?.toLowerCase().includes(searchQuery.toLowerCase(), 0)
);
setMyOffersLength(newOffers?.length);
newOffers = newOffers.slice((page - 1) * 10, page * 10);
}
return newOffers;
}, [
pinnedOffersToShow,
offersToShow,
myOffers,
page,
searchQuery,
isApplied,
]);

// Total number of all offers that can be shown
const totalOffers = useMemo(() => {
if (myOffers) {
return myOffersLength;
}
return total;
}, [total, myOffersLength]);

const searchMyOffers = (searchValue) => {
setSearchQuery(searchValue);
};

// Changing page
const handleDifferentPage = (pageNum) => {
setPage(pageNum);
};

// Refetching offers based on query string
const refetch = () => {
if (!myOffers) {
dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString }));
history.replace({
pathname: HOME_PAGE,
search: queryStringHook.getGlobalQueryString(),
});
} else {
dispatch(fetchMineOffers());
}
window.scrollTo({
top: 0,
behavior: "smooth",
});
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has(KEY_PAGE)) {
if (queryObject.get(KEY_PAGE) !== page.toString())
setPage(parseInt(queryObject.get(KEY_PAGE)));
} else {
setPage(1);
}
};

return {
handleDifferentPage,
totalOffers,
allOffersToShow,
page,
searchMyOffers,
};
};
export default useOffers;

+ 66
- 0
src/hooks/useOffers/useCategoryFilter.js Zobrazit soubor

@@ -0,0 +1,66 @@
import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setFilteredCategory } from "../../store/actions/filters/filtersActions";
import { selectCategories } from "../../store/selectors/categoriesSelectors";
import { selectSelectedCategory } from "../../store/selectors/filtersSelectors";

const useCategoryFilter = () => {
const selectedCategory = useSelector(selectSelectedCategory);
const allCategories = useSelector(selectCategories);
const dispatch = useDispatch();
const [selectedCategoryLocally, setSelectedCategoryLocally] = useState({});
const initialOption = useMemo(() => {
return {
_id: 0,
};
}, []);

useEffect(() => {
setSelectedCategoryLocally(selectedCategory);
}, [selectedCategory]);

// Set selected category locally in state
// If second argument is true, then selected category is also updated in redux
const setSelectedCategory = (category, immediateApply = false) => {
setSelectedCategoryLocally(category);
if (immediateApply) {
dispatch(setFilteredCategory(category));
}
};

// Find category object by providing its name
const findCategory = (categoryName) => {
return allCategories.find((category) => category.name === categoryName);
};

// Get all subcategories by providing its category name
const getSubcategories = (categoryName) => {
let category = findCategory(categoryName);
return category?.subcategories ? category.subcategories : [];
};


// Update selected category in redux
const apply = () => {
dispatch(setFilteredCategory(selectedCategoryLocally));
};

// Clear category chosen
const clear = () => {
setSelectedCategoryLocally(initialOption);
dispatch(setFilteredCategory(initialOption));
};

return {
selectedCategory,
selectedCategoryLocally,
setSelectedCategory,
getSubcategories,
findCategory,
allCategories,
apply,
clear,
};
};

export default useCategoryFilter;

+ 50
- 0
src/hooks/useOffers/useFilters.js Zobrazit soubor

@@ -0,0 +1,50 @@
import { useEffect, useMemo } from "react";
import useCategoryFilter from "./useCategoryFilter";
import useLocationsFilter from "./useLocationsFilter";
import useSubcategoryFilter from "./useSubcategoryFilter";

const useFilters = (clearAll = false) => {
const category = useCategoryFilter();
const subcategory = useSubcategoryFilter();
const locations = useLocationsFilter();

useEffect(() => {
if (clearAll) {
clear();
}
}, []);

const numOfFiltersChosen = useMemo(() => {
let sumOfFiltersChosen = 0;
if (category.selectedCategoryLocally?._id) sumOfFiltersChosen++;
if (subcategory.selectedSubcategoryLocally?._id) sumOfFiltersChosen++;
sumOfFiltersChosen += locations.selectedLocationsLocally.length;
return sumOfFiltersChosen;
}, [
category.selectedCategoryLocally,
subcategory.selectedSubcategoryLocally,
locations.selectedLocationsLocally,
]);

const apply = () => {
category.apply();
subcategory.apply();
locations.apply();
};

const clear = () => {
category.clear();
subcategory.clear();
locations.clear();
};

return {
category,
subcategory,
locations,
numOfFiltersChosen,
apply,
clear,
};
};
export default useFilters;

+ 54
- 0
src/hooks/useOffers/useLocationsFilter.js Zobrazit soubor

@@ -0,0 +1,54 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setFilteredLocations } from "../../store/actions/filters/filtersActions";
import { selectSelectedLocations } from "../../store/selectors/filtersSelectors";
import { selectLocations } from "../../store/selectors/locationsSelectors";

const useLocationsFilter = () => {
const selectedLocations = useSelector(selectSelectedLocations);
const dispatch = useDispatch();
const allLocations = useSelector(selectLocations);
const [selectedLocationsLocally, setSelectedLocationsLocally] = useState([]);

useEffect(() => {
setSelectedLocationsLocally(selectedLocations);
}, [selectedLocations]);

// Set selected locations globally
const setSelectedLocations = (locations, immediateApply = false) => {
setSelectedLocationsLocally(locations);
if (immediateApply) {
dispatch(setFilteredLocations(locations));
}
};

// Find locations from array made from query string, and set locations globally
const setSelectedLocationsFromArray = (locations) => {
let locationsToPush = [];
locations.forEach((locationName) => {
locationsToPush.push(allLocations.find((p) => p.city === locationName));
});
setSelectedLocations([...locationsToPush])
};

const apply = () => {
dispatch(setFilteredLocations(selectedLocationsLocally));
};

const clear = () => {
setSelectedLocationsLocally([]);
dispatch(setFilteredLocations([]));
};

return {
selectedLocations,
selectedLocationsLocally,
setSelectedLocations,
setSelectedLocationsFromArray,
allLocations,
apply,
clear,
};
};

export default useLocationsFilter;

+ 101
- 0
src/hooks/useOffers/useMyOffers.js Zobrazit soubor

@@ -0,0 +1,101 @@
import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { sortEnum } from "../../enums/sortEnum";
import { fetchMineOffers } from "../../store/actions/offers/offersActions";
import { selectMineOffers } from "../../store/selectors/offersSelectors";
import useFilters from "./useFilters";
import usePaging from "./usePaging";
import useSearch from "./useSearch";
import useSorting from "./useSorting";

const useMyOffers = () => {
const filters = useFilters(true);
const sorting = useSorting();
const mineOffers = useSelector(selectMineOffers);
const search = useSearch();
const dispatch = useDispatch();
const paging = usePaging();
const [appliedFilters, setAppliedFilters] = useState(false);
const [totalOffers, setTotalOffers] = useState(0);

useEffect(() => {
dispatch(fetchMineOffers());
}, []);

const apply = () => {
paging.changePage(1);
setAppliedFilters(false);
};

// Filter, search and sort all mine offers
const allOffersToShow = useMemo(() => {
let mineOffersFiltered = [...mineOffers];
// Filter mine offers by category
if (filters.category.selectedCategoryLocally?.name)
mineOffersFiltered = mineOffersFiltered.filter(
(offer) =>
offer?.category?.name ===
filters.category.selectedCategoryLocally.name
);
// Filter mine offers by subcategory
if (filters.subcategory.selectedSubcategoryLocally?.name) {
mineOffersFiltered = mineOffersFiltered.filter(
(offer) =>
offer?.subcategory ===
filters.subcategory.selectedSubcategoryLocally?.name
);
}
// Filter mine offers by locations
if (filters.locations.selectedLocationsLocally?.length > 0) {
mineOffersFiltered = mineOffersFiltered.filter((offer) =>
filters.locations.selectedLocationsLocally.find(
(location) => location?.city === offer?.location?.city
)
);
}
// Sort mine offers
if (sorting.selectedSortOptionLocally.value !== sortEnum.INITIAL.value) {
if (sorting.selectedSortOptionLocally.value === sortEnum.OLD.value) {
mineOffersFiltered.sort(
(a, b) => new Date(a._created) - new Date(b._created)
);
}
if (sorting.selectedSortOptionLocally.value === sortEnum.NEW.value) {
mineOffersFiltered.sort(
(a, b) => new Date(b._created) - new Date(a._created)
);
}
if (sorting.selectedSortOptionLocally.value === sortEnum.POPULAR.value) {
mineOffersFiltered.sort((a, b) => b.views.count - a.views.count);
}
}
mineOffersFiltered = mineOffersFiltered.filter((offer) =>
offer?.name?.toLowerCase()?.includes(search.searchStringLocally)
);
setTotalOffers(mineOffersFiltered?.length);
mineOffersFiltered = mineOffersFiltered.slice(
(paging.currentPage - 1) * 10,
paging.currentPage * 10
);
if (!appliedFilters) {
setAppliedFilters(true);
}
return [...mineOffersFiltered];
}, [
appliedFilters,
sorting.selectedSortOptionLocally,
mineOffers,
paging.currentPage,
]);

return {
filters,
paging,
sorting,
search,
allOffersToShow,
totalOffers,
apply,
};
};
export default useMyOffers;

+ 144
- 0
src/hooks/useOffers/useOffers.js Zobrazit soubor

@@ -0,0 +1,144 @@
import { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
KEY_CATEGORY,
KEY_LOCATION,
KEY_PAGE,
KEY_SEARCH,
KEY_SORTBY,
KEY_SUBCATEGORY,
} from "../../constants/queryStringConstants";
import { fetchCategories } from "../../store/actions/categories/categoriesActions";
import { fetchLocations } from "../../store/actions/locations/locationsActions";
import {
selectOffers,
selectTotalOffers,
} from "../../store/selectors/offersSelectors";
import useFilters from "./useFilters";
import useQueryString from "./useQueryString";
import { setQueryString } from "../../store/actions/queryString/queryStringActions";
import {
convertQueryStringForBackend,
makeHeaderStringHelper,
makeQueryStringHelper,
} from "../../util/helpers/queryHelpers";
import useSorting from "./useSorting";
import useSearch from "./useSearch";
import {
setHeaderString,
setSearchString,
} from "../../store/actions/filters/filtersActions";
import usePaging from "./usePaging";
import { useHistory } from "react-router-dom";

const useOffers = () => {
const dispatch = useDispatch();
const filters = useFilters();
const queryStringHook = useQueryString();
const offers = useSelector(selectOffers);
const totalOffers = useSelector(selectTotalOffers);
const history = useHistory();

// Always fetch categories and locations,
// becouse count of total offers change over time
useEffect(() => {
dispatch(fetchCategories());
dispatch(fetchLocations());
return () => clear();
}, []);

useEffect(() => {
console.log('location: ', history.location)
if (history.location.state?.logo) {
console.log('logo');
clear();
}
}, [history.location])

// On every change of query string, new header string should be created
// Header string is shown on Home page above offers
useEffect(() => {
const headerStringLocal = makeHeaderStringHelper(filters);
dispatch(setHeaderString(headerStringLocal));
}, [queryStringHook.queryString]);

// Initially set category, location and subcategory based on query string
useEffect(() => {
if (queryStringHook.isInitiallyLoaded) {
const queryObject = queryStringHook.queryObject;
if (KEY_CATEGORY in queryObject) {
const category = filters.category.findCategory(
queryObject[KEY_CATEGORY]
);
filters.category.setSelectedCategory(category);
if (KEY_SUBCATEGORY in queryObject) {
const subcategory = filters.category
.getSubcategories(category?.name)
.find(
(subcategory) => subcategory.name === queryObject[KEY_SUBCATEGORY]
);
filters.subcategory.setSelectedSubcategory(subcategory);
}
}
if (KEY_LOCATION in queryObject) {
filters.locations.setSelectedLocationsFromArray(
queryObject[KEY_LOCATION]
);
}
if (KEY_SORTBY in queryObject) {
sorting.changeSortingFromName(queryObject[KEY_SORTBY]);
}
if (KEY_PAGE in queryObject) {
if (queryObject[KEY_PAGE] !== 1)
paging.changePage(queryObject[KEY_PAGE]);
}
dispatch(setSearchString(queryObject[KEY_SEARCH]));
}
}, [queryStringHook.isInitiallyLoaded]);

const allOffersToShow = useMemo(() => {
return offers;
}, [offers]);

const apply = () => {
filters.apply();
const newQueryString = makeQueryStringHelper(
filters,
paging,
search,
sorting
);
dispatch(setQueryString(convertQueryStringForBackend(newQueryString)));
};

// Those hooks are below becouse function apply cannot be put on props before initialization
const sorting = useSorting(apply);
const paging = usePaging(apply);
const search = useSearch(apply);

// On every change of search string, offers should be immediately searched
useEffect(() => {
if (queryStringHook.isInitiallyLoaded) {
search.searchOffers(search.searchString);
}
}, [search.searchString]);

const clear = () => {
filters.clear();
sorting.clear();
paging.changePage(1);
};

return {
filters,
sorting,
paging,
queryStringHook,
allOffersToShow,
totalOffers,
apply,
clear,
};
};

export default useOffers;

+ 38
- 0
src/hooks/useOffers/usePaging.js Zobrazit soubor

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

const usePaging = (applyAllFilters) => {
const [currentPage, setCurrentPage] = useState(1);
const [isInitallyLoaded, setIsInitiallyLoaded] = useState(false);

// If state currentPage is changed, new request to backend should be sent,
// except on initial load
useEffect(() => {
if (isInitallyLoaded && applyAllFilters) {
applyAllFilters();
}
window.scrollTo({
top: 0,
behavior: "smooth",
});
}, [currentPage]);

const changePage = (pageNumber) => {
setCurrentPage(pageNumber);
setIsInitiallyLoaded(true);
};

const goToNextPage = () => {
setCurrentPage((prevPage) => prevPage + 1);
};
const goToPrevPage = () => {
setCurrentPage((prevPage) => prevPage - 1);
};

return {
currentPage,
changePage,
goToNextPage,
goToPrevPage,
};
};
export default usePaging;

+ 60
- 0
src/hooks/useOffers/useQueryString.js Zobrazit soubor

@@ -0,0 +1,60 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import { fetchOffers } from "../../store/actions/offers/offersActions";
import { setQueryString } from "../../store/actions/queryString/queryStringActions";
import { selectQueryString } from "../../store/selectors/queryStringSelectors";
import {
convertQueryStringForBackend,
convertQueryStringForFrontend,
getQueryObjectHelper,
} from "../../util/helpers/queryHelpers";

const useQueryString = () => {
const queryString = useSelector(selectQueryString);
const history = useHistory();
const dispatch = useDispatch();
const [isInitiallyLoaded, setIsInitallyLoaded] = useState(false);
const [queryObject, setQueryObject] = useState({});

// Initially read filters, sorting and paging from querystring
useEffect(() => {
if ((!isInitiallyLoaded || history.location?.state?.logo) && !history.location?.state?.from) {
const queryStringFromUrl = history.location?.search;
setQueryObject(getQueryObjectHelper(queryStringFromUrl));
dispatch(setQueryString(queryStringFromUrl));
}
history.location.state = {}
}, [history.location]);

// Set initially loaded to true on initial load
useEffect(() => {
if (
convertQueryStringForFrontend(queryString) ===
convertQueryStringForFrontend(history.location.search) &&
!isInitiallyLoaded
) {
setIsInitallyLoaded(true);
}
}, [queryString]);

// Updating offers on query string change
useEffect(() => {
if (isInitiallyLoaded) {
dispatch(
fetchOffers({ queryString: convertQueryStringForBackend(queryString) })
);
setQueryObject(getQueryObjectHelper(queryString));
history.replace({
search: convertQueryStringForFrontend(queryString),
});
}
}, [queryString, isInitiallyLoaded]);

return {
queryString,
queryObject,
isInitiallyLoaded,
};
};
export default useQueryString;

+ 46
- 0
src/hooks/useOffers/useSearch.js Zobrazit soubor

@@ -0,0 +1,46 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setSearchString } from "../../store/actions/filters/filtersActions";
import { selectSearchString } from "../../store/selectors/filtersSelectors";

const useSearch = (applyAllFilters) => {
const [searchStringLocally, setSearchStringLocally] = useState("");
const [isInitallyLoaded, setIsInitiallyLoaded] = useState(false);
const dispatch = useDispatch();
const searchString = useSelector(selectSearchString);

// On every global change of search string, new request to backend should be sent
useEffect(() => {
if (searchStringLocally !== searchString && applyAllFilters) {
setSearchStringLocally(searchString);
}
if (isInitallyLoaded) {
if (applyAllFilters) applyAllFilters();
}
}, [searchString]);

// On every local change of search string, global state of search string should be also updated
useEffect(() => {
if (isInitallyLoaded && applyAllFilters) {
dispatch(setSearchString(searchStringLocally));
}
}, [searchStringLocally]);

const searchOffers = (searchValue) => {
setIsInitiallyLoaded(true);
setSearchStringLocally(searchValue);
};

const clear = () => {
setSearchStringLocally("");
};

return {
searchOffers,
setSearchStringLocally,
searchStringLocally,
searchString,
clear,
};
};
export default useSearch;

+ 65
- 0
src/hooks/useOffers/useSorting.js Zobrazit soubor

@@ -0,0 +1,65 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
VALUE_SORTBY_NEW,
VALUE_SORTBY_OLD,
VALUE_SORTBY_POPULAR,
} from "../../constants/queryStringConstants";
import { sortEnum } from "../../enums/sortEnum";
import { setFilteredSortOption } from "../../store/actions/filters/filtersActions";
import { selectSelectedSortOption } from "../../store/selectors/filtersSelectors";

const useSorting = (applyAllFilters) => {
const selectedSortOption = useSelector(selectSelectedSortOption);
const [selectedSortOptionLocally, setSelectedSortOptionLocally] = useState(
sortEnum.INITIAL
);
const [isInitiallyLoaded, setIsInitallyLoaded] = useState(false);
const dispatch = useDispatch();

// On every change of sorting option, new request to backend should be sent
useEffect(() => {
if (isInitiallyLoaded) {
if (applyAllFilters) applyAllFilters();
}
}, [isInitiallyLoaded, selectedSortOption]);

const changeSorting = (newSortOption) => {
dispatch(setFilteredSortOption(newSortOption));
setSelectedSortOptionLocally(newSortOption);
if (!isInitiallyLoaded) {
setIsInitallyLoaded(true);
}
};

// Change sorting by name of sorting option that is shown on frontned
const changeSortingFromName = (sortingName) => {
if (sortingName === VALUE_SORTBY_NEW) {
changeSorting(sortEnum.NEW, true);
}
if (sortingName === VALUE_SORTBY_OLD) {
changeSorting(sortEnum.OLD, true);
}
if (sortingName === VALUE_SORTBY_POPULAR) {
changeSorting(sortEnum.POPULAR, true);
}
};

const apply = () => {
// For future changes
};

const clear = () => {
dispatch(setFilteredSortOption(sortEnum.INITIAL));
};

return {
selectedSortOption,
selectedSortOptionLocally,
changeSorting,
changeSortingFromName,
apply,
clear,
};
};
export default useSorting;

+ 56
- 0
src/hooks/useOffers/useSubcategoryFilter.js Zobrazit soubor

@@ -0,0 +1,56 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setFilteredSubcategory } from "../../store/actions/filters/filtersActions";
import { selectSelectedSubcategory } from "../../store/selectors/filtersSelectors";

const useSubcategoryFilter = () => {
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const dispatch = useDispatch();
const initialOption = {
label: "SVE PODKATEGORIJE",
_id: 0,
};
const [selectedSubcategoryLocally, setSelectedSubcategoryLocally] =
useState(initialOption);

useEffect(() => {
if (selectedSubcategory)
if ("_id" in selectedSubcategory) {
setSelectedSubcategoryLocally(selectedSubcategory);
}
}, [selectedSubcategory]);

useEffect(() => {
if (selectedSubcategoryLocally)
if (Object.keys(selectedSubcategoryLocally)?.length === 0) {
setSelectedSubcategoryLocally(initialOption);
}
}, [initialOption]);

const setSelectedSubcategory = (subcategory, immediateApply = false) => {
setSelectedSubcategoryLocally(subcategory);
if (immediateApply) {
dispatch(setFilteredSubcategory(subcategory));
}
};

const apply = () => {
dispatch(setFilteredSubcategory(selectedSubcategoryLocally));
};

const clear = () => {
setSelectedSubcategoryLocally(initialOption);
dispatch(setFilteredSubcategory(initialOption));
};

return {
selectedSubcategory,
selectedSubcategoryLocally,
setSelectedSubcategory,
initialOption,
apply,
clear,
};
};

export default useSubcategoryFilter;

+ 0
- 174
src/hooks/useQueryString.js Zobrazit soubor

@@ -1,174 +0,0 @@
/* eslint-disable */
import _ from "lodash";
import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../constants/pages";
import { setQueryString as setQueryStringSaga } from "../store/actions/queryString/queryStringActions";
import { selectQueryString } from "../store/selectors/queryStringSelectors";
import {
convertQueryStringBackend,
convertQueryStringFrontend,
} from "../util/helpers/queryHelpers";
import { KEY_CATEGORY, KEY_LOCATION, KEY_PAGE, KEY_SIZE, KEY_SORTBY, KEY_SORT_DATE, KEY_SORT_POPULAR, KEY_SUBCATEGORY } from "../constants/queryStringConstants";

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

// Initially loading query string and putting it into redux
useEffect(() => {
// Substring(1) used becouse query string in initial state is ?key=value
// and in redux query string is stored as only key=value
const queryStringLocal = history.location.search.substring(1);
setQueryString(convertQueryStringBackend(queryStringLocal));
setGlobalQueryString(queryStringLocal);
}, []);

// Setting loadedFromUrl to true when query string is loaded and filters/sorting/search
// has been changed so global query string matches current query string
useEffect(() => {
if (globalQueryString === history.location.search.substring(1))
setLoadedFromURL(true);
}, [globalQueryString]);

// Making function to initially set global query string when all filters and sorting
// has been set
const fun = useMemo(() => {
return _.once(() => {
setGlobalQueryString(convertQueryStringFrontend(queryString));
setInitial(false);
});
}, [queryString]);

// Setting global query string when all filters and sorting has been set
useEffect(() => {
if (initial && loadedFromURL) {
if (queryString?.length > 0) {
fun();
}
} else {
setGlobalQueryString(convertQueryStringFrontend(queryString));
}
}, [queryString, loadedFromURL]);

// When global query string is changed, updating query string that user sees
useEffect(() => {
if (!initial && history.location.pathname === HOME_PAGE) {
history.replace({
pathname: HOME_PAGE,
search: "?" + globalQueryString,
});
}
}, [globalQueryString, initial]);

const getQueryString = () => {
return queryString;
};
const setQueryString = (newQueryString) => {
dispatch(setQueryStringSaga(newQueryString));
};
const getQueryObject = () => {
const urlParams = new URLSearchParams(queryString);
return Object.fromEntries(urlParams);
};
// Adding key-value pairs to query string, deletes its previous duplicates, except
// when working with locations, then just appends it, or doesnt append if query string
// already contains provided location
const appendToQueryString = (key, value) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (key === KEY_LOCATION) {
if (urlParams.has(key)) {
let arrayOfLocations = urlParams.getAll(key);
if (arrayOfLocations.includes(value)) {
arrayOfLocations = arrayOfLocations.filter(
(item) => item?.toString() !== value?.toString()
);
urlParams.delete(key);
arrayOfLocations.forEach((item) => {
urlParams.append(key, item);
});
}
}
} else {
if (urlParams.has(key)) {
urlParams.delete(key);
}
}
if (!value) setQueryString(urlParams.toString());
urlParams.append(key, value);
setQueryString(urlParams.toString());
return urlParams.toString();
}
};
// Same as appendToQueryString, just adds multiple key-value pairs at once
const appendMultipleToQueryString = (array = []) => {
if (loadedFromURL) {
let urlParams = new URLSearchParams(queryString);
if (
array.find((item) => item.key === KEY_CATEGORY) ||
array.find((item) => item.key === KEY_SUBCATEGORY)
) {
urlParams.delete(KEY_LOCATION);
}
array.forEach((item) => {
if (urlParams.has(item.key) && item.key !== KEY_LOCATION) {
urlParams.delete(item.key);
}
if (!item.value) return;
urlParams.append(item.key, item.value);
});
setQueryString(urlParams.toString());
return urlParams.toString();
}
};
const deleteFromQueryString = (key, value = null) => {
let urlParams = new URLSearchParams(queryString);
if (key === KEY_LOCATION) {
let arrayOfLocations = urlParams.getAll(key);
arrayOfLocations = arrayOfLocations.filter((item) => item !== value);
urlParams.delete(key);
arrayOfLocations.forEach((item) => {
urlParams.append(key, item);
});
} else if (key === KEY_SORTBY) {
urlParams.delete(KEY_SORT_DATE);
urlParams.delete(KEY_SORT_POPULAR);
} else {
urlParams.delete(key);
}
setQueryString(urlParams.toString());
return urlParams.toString();
};
const getInitialQueryString = () => {
let urlParams = new URLSearchParams(queryString);
urlParams = new URLSearchParams(appendToQueryString(KEY_SIZE, 10));
urlParams = new URLSearchParams(appendToQueryString(KEY_PAGE, 1));
return urlParams;
};

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

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

+ 11
- 11
src/hooks/useSearch.js Zobrazit soubor

@@ -1,16 +1,16 @@
import { useQueryString } from "./useQueryString";
// import useQueryString from "./useOffers/useQueryString";

export const useSearch = () => {
const queryStringHook = useQueryString();
const searchOffers = (searchString) => {
if (searchString?.length !== 0) {
queryStringHook.appendToQueryString("oname", searchString);
} else {
const newQueryString = new URLSearchParams(queryStringHook.queryString);
if (newQueryString.has("oname")) {
queryStringHook.deleteFromQueryString("oname");
}
}
// const queryStringHook = useQueryString();
const searchOffers = () => {
// if (searchString?.length !== 0) {
// queryStringHook.appendToQueryString("oname", searchString);
// } else {
// const newQueryString = new URLSearchParams(queryStringHook.queryString);
// if (newQueryString.has("oname")) {
// queryStringHook.deleteFromQueryString("oname");
// }
// }
};

return {

+ 36
- 0
src/hooks/useSkeleton.js Zobrazit soubor

@@ -0,0 +1,36 @@
import { useCallback, useEffect, useMemo, useState } from "react";

const useSkeleton = (skeletonOptions) => {
const [transitionStage, setTransitionStage] = useState(1);

// After how long skeleton changes screen
const timeoutInterval = useMemo(() => {
return skeletonOptions?.timeoutInterval || 900;
});

// isLoadingIndicator is boolean that indicates should skeleton screen be shown
const isLoadingIndicator = useMemo(() => {
return skeletonOptions?.isLoadingIndicator;
}, [skeletonOptions]);

// Timeout function to change transition stage
const timeout = useCallback(() => {
setTransitionStage((prevTransitionStage) => {
if (prevTransitionStage === 2) return 1;
return prevTransitionStage + 1;
});
}, [transitionStage]);

useEffect(() => {
let newTimeout;
if (isLoadingIndicator) {
newTimeout = setTimeout(timeout, timeoutInterval);
}
return () => clearTimeout(newTimeout);
}, [timeout, isLoadingIndicator]);

return {
transitionStage,
};
};
export default useSkeleton;

+ 0
- 86
src/hooks/useSorting.js Zobrazit soubor

@@ -1,86 +0,0 @@
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { sortEnum } from "../enums/sortEnum";
import { setFilteredSortOption } from "../store/actions/filters/filtersActions";
import { selectSelectedSortOption } from "../store/selectors/filtersSelectors";
import { useQueryString } from "./useQueryString";
import { convertQueryStringFrontend } from "../util/helpers/queryHelpers";
import {
KEY_PAGE,
KEY_SORTBY,
KEY_SORT_DATE,
KEY_SORT_POPULAR,
VALUE_SORTBY_NEW,
VALUE_SORTBY_OLD,
VALUE_SORTBY_POPULAR,
} from "../constants/queryStringConstants";

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

// Setting sort option on initially load or refresh page
useEffect(() => {
if (queryStringHook.loadedFromURL) {
const queryString = queryStringHook.queryString;
let queryObject = new URLSearchParams(
convertQueryStringFrontend(queryString)
);
if (queryObject.has(KEY_SORTBY)) {
if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_NEW) {
setSelectedSortOption(sortEnum.NEW);
}
if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_OLD) {
setSelectedSortOption(sortEnum.OLD);
}
if (queryObject.get(KEY_SORTBY) === VALUE_SORTBY_POPULAR) {
setSelectedSortOption(sortEnum.POPULAR);
}
} else {
setSelectedSortOption(sortOptions.INITIAL);
}
}
}, [queryStringHook.queryString, queryStringHook.loadedFromURL]);

const setSelectedSortOption = (payload, shouldGoFirstPage = false) => {
dispatch(setFilteredSortOption(payload));
let _des_date = null;
let _des_popular = null;
if (payload.value === sortOptions.NEW.value) {
_des_date = true;
}
if (payload.value === sortOptions.OLD.value) {
_des_date = false;
}
if (payload.value === sortOptions.POPULAR.value) {
_des_popular = true;
}
let queryArray = [];
if (_des_date !== null) {
queryArray.push({ key: KEY_SORT_DATE, value: `${_des_date}` });
queryArray.push({ key: KEY_SORT_POPULAR });
}
if (_des_popular !== null) {
queryArray.push({ key: KEY_SORT_POPULAR, value: `${_des_popular}` });
queryArray.push({ key: KEY_SORT_DATE });
}
if (shouldGoFirstPage) {
queryArray.push({ key: KEY_PAGE, value: "1" });
}
queryStringHook.appendMultipleToQueryString(queryArray);
};

const changeSorting = (payload) => {
setSelectedSortOption(payload, true);
};

return {
selectedSortOption,
setSelectedSortOption,
sortOptions,
changeSorting,
};
};
export default useSorting;

+ 16
- 3
src/i18n/resources/rs.js Zobrazit soubor

@@ -96,7 +96,7 @@ export default {
PIBnoOfCharacters: "PIB mora imati 9 karaktera!",
welcome: "Dobro došli na trampu, želimo vam uspešno trampovanje!",
imageError: "Slika je obavezna!",
serverError: "Greška sa serverom!"
serverError: "Greška sa serverom!",
},
forgotPassword: {
title: "Povrati lozinku",
@@ -160,6 +160,9 @@ export default {
"Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>",
continue: "NASTAVI",
publish: "OBJAVI",
review: "Pregled",
changeOffer: "Izmena Objave",
newOffer: "Nova Objava",
},
apiErrors: {
somethingWentWrong: "Greska sa serverom!",
@@ -195,7 +198,7 @@ export default {
miniChatHeaderTitle: "Moje Poruke",
send: "Pošalji",
sendPlaceholder: "Poruka...",
seeChats: "Pogledaj ćaskanje"
seeChats: "Pogledaj ćaskanje",
},
editProfile: {
website: "Web Sajt*",
@@ -207,6 +210,7 @@ export default {
labelNameRequired: "Ime firme je obavezno!",
labelPIBRequired: "PIB firme je obavezan!",
labelLocationRequired: "Lokacija je obavezna!",
labelPhoneValid: "Unesite validan broj telefona",
labelPhoneRequired: "Broj telefona je obavezan!",
},
deleteOffer: {
@@ -222,7 +226,7 @@ export default {
totalViews: " ukupnih pregleda",
successfulExchanges: " uspešnih trampi",
correctCommunications: " korektna komunikacija",
headerTitle: "Nazad na objave"
headerTitle: "Nazad na objave",
},
notFound: {
error404: "Greška 404",
@@ -236,4 +240,13 @@ export default {
"Nažalost ne postoji ni jedna objava <br /> za unete kriterijume.",
showAllOffers: "Pogledaj sve objave",
},
profile: {
myProfile: "Moj profil",
PIB: "PIB:",
publishes: " objava",
successExchange: " uspešna trampa",
numberOfViews: " ukupnih pregleda",
successComunication: " korektna komunikacija",
back: "Nazad na objave",
},
};

+ 4
- 4
src/pages/ErrorPages/NotFoundPage.js Zobrazit soubor

@@ -3,6 +3,7 @@ import { Trans, useTranslation } from "react-i18next";
import {
Container,
ErrorContainer,
ErrorImageContainer,
ErrorHeading,
ErrorMessage,
Button,
@@ -28,17 +29,16 @@ const NotFoundPage = () => {
return (
<Container>
<ErrorContainer>
<Error404 />
<ErrorImageContainer>
<Error404 />
</ErrorImageContainer>
<ErrorHeading>{t("notFound.error404")}</ErrorHeading>
<ErrorMessage>
<Trans i18nKey="notFound.errorMessage" />
</ErrorMessage>
<Button
variant="contained"
width="190px"
height="49px"
buttoncolor={selectedTheme.primaryYellow}
textcolor="black"
onClick={showAllOffersHandler}
>
{t("notFound.showAllOffers")}

+ 24
- 3
src/pages/ErrorPages/NotFoundPage.styles.js Zobrazit soubor

@@ -2,7 +2,6 @@ import { Typography } from "@mui/material";
import { Box } from "@mui/system";
import styled from "styled-components";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
// import Section from "../../components/Section/Section";
import selectedTheme from "../../themes";

export const Container = styled(Box)`
@@ -19,14 +18,24 @@ export const ErrorContainer = styled(Box)`
height: 100vh;
`;

export const ErrorImageContainer = styled(Box)`
@media screen and (max-width: 600px) {
width: 196px;
svg {
width: 196px;
height: 90px;
}
}
`;

export const ErrorHeading = styled(Typography)`
font-family: "Open Sans";
font-size: 72px;
font-weight: 700;
color: ${selectedTheme.primaryPurple};

@media screen and (max-width: 420px) {
font-size: 62px;
@media screen and (max-width: 600px) {
font-size: 36px;
}
`;

@@ -36,8 +45,20 @@ export const ErrorMessage = styled(Typography)`
font-weight: 400;
color: #818181;
margin-bottom: 45px;

@media screen and (max-width: 600px) {
font-size: 14px;
}
`;

export const Button = styled(PrimaryButton)`
width: 190px;
height: 49px;
font-weight: 600;
color: #000;

@media screen and (max-width: 600px) {
width: 180px;
height: 44px;
}
`;

+ 39
- 4
src/pages/HomePage/HomePageMUI.js Zobrazit soubor

@@ -1,15 +1,50 @@
import React from "react";
import React, { useState } from "react";
import { HomePageContainer } from "./HomePage.styled";
import FilterCard from "../../components/Cards/FilterCard/FilterCard";
import MainLayout from "../../layouts/MainLayout/MainLayout";
import MarketPlace from "../../components/MarketPlace/MarketPlace";
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
import { useSelector } from "react-redux";
import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants";
import useOffers from "../../hooks/useOffers/useOffers";
import useSkeleton from "../../hooks/useSkeleton";

const HomePage = () => {
const isLoadingOffers = useSelector(
selectIsLoadingByActionType(OFFERS_SCOPE)
);
const [filtersOpened, setFiltersOpened] = useState(false);
const offers = useOffers();
const { transitionStage } = useSkeleton({
timeoutInterval: 900,
isLoadingIndicator: isLoadingOffers,
});
const toggleFilters = () => {
setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened);
};

return (
<HomePageContainer>
<MainLayout leftCard={<FilterCard />} content={<MarketPlace />} />
<MainLayout
leftCard={
<FilterCard
offers={offers}
filtersOpened={filtersOpened}
skeleton={isLoadingOffers}
animationStage={transitionStage}
toggleFilters={toggleFilters}
/>
}
content={
<MarketPlace
offers={offers}
skeleton={isLoadingOffers}
animationStage={transitionStage}
toggleFilters={toggleFilters}
/>
}
/>
</HomePageContainer>
);
}
};
export default HomePage;


+ 5
- 3
src/pages/ItemDetailsPage/ItemDetailsPageMUI.js Zobrazit soubor

@@ -1,22 +1,24 @@
import React, { useEffect } from "react";
import { PropTypes } from "prop-types";
import { ItemDetailsPageContainer } from "./ItemDetailsPage.styled";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import ItemDetails from "../../components/ItemDetails/ItemDetails";
import ItemDetailsLayout from "../../layouts/ItemDetailsLayout/ItemDetailsLayout";
import { fetchOneOffer } from "../../store/actions/offers/offersActions";
import UserReviews from "../../components/UserReviews/UserReviews";
import { selectOffer } from "../../store/selectors/offersSelectors";

const ItemDetailsPage = (props) => {
const dispatch = useDispatch();
const selectedOffer = useSelector(selectOffer);

const offerId = props.match.params.idProizvod;

useEffect(() => {
if (offerId) {
if (offerId && !selectedOffer?.offer) {
dispatch(fetchOneOffer(offerId));
}
}, [offerId]);
}, [offerId, selectedOffer?.offer]);

return (
<ItemDetailsPageContainer>

+ 38
- 3
src/pages/MyOffers/MyOffers.js Zobrazit soubor

@@ -1,16 +1,51 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import { MyOffersContainer } from "./MyOffers.styled";
import MainLayout from "../../layouts/MainLayout/MainLayout";
import FilterCard from "../../components/Cards/FilterCard/FilterCard";
import MarketPlace from "../../components/MarketPlace/MarketPlace";
import useMyOffers from "../../hooks/useOffers/useMyOffers";
import useSkeleton from "../../hooks/useSkeleton";
import { useSelector } from "react-redux";
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
import { OFFERS_MINE_SCOPE } from "../../store/actions/offers/offersActionConstants";

const MyOffers = () => {
const offers = useMyOffers();
const isLoadingMineOffers = useSelector(
selectIsLoadingByActionType(OFFERS_MINE_SCOPE)
);
const [filtersOpened, setFiltersOpened] = useState(false);

const { transitionStage } = useSkeleton({
timeoutInterval: 900,
isLoadingIndicator: isLoadingMineOffers,
});
const toggleFilters = () => {
setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened);
};
return (
<MyOffersContainer>
<MainLayout
leftCard={<FilterCard myOffers />}
content={<MarketPlace myOffers={true} />}
leftCard={
<FilterCard
myOffers
offers={offers}
animationStage={transitionStage}
filtersOpened={filtersOpened}
toggleFilters={toggleFilters}
skeleton={isLoadingMineOffers}
/>
}
content={
<MarketPlace
myOffers={true}
offers={offers}
animationStage={transitionStage}
skeleton={isLoadingMineOffers}
toggleFilters={toggleFilters}
/>
}
/>
</MyOffersContainer>
);

+ 2
- 1
src/store/actions/app/appActionConstants.js Zobrazit soubor

@@ -1,4 +1,5 @@
import { createLoadingType } from '../actionHelpers';

export const APP_LOADING = createLoadingType('APP_LOADING');
export const UPDATE_LOADER = createLoadingType("UPDATE_LOADER")
export const ADD_LOADER = createLoadingType("ADD_LOADER");
export const REMOVE_LOADER = createLoadingType("REMOVE_LOADER");

+ 8
- 4
src/store/actions/app/appActions.js Zobrazit soubor

@@ -1,6 +1,10 @@
import { APP_LOADING } from './appActionConstants';
import { ADD_LOADER, REMOVE_LOADER } from "./appActionConstants";

export const setAppReady = (payload) => ({
type: APP_LOADING,
payload: payload
export const addLoader = (payload) => ({
type: ADD_LOADER,
payload
});
export const removeLoader = (payload) => ({
type: REMOVE_LOADER,
payload
})

+ 4
- 2
src/store/actions/categories/categoriesActionConstants.js Zobrazit soubor

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

const CATEGORIES_SCOPE = "CATEGORIES";
export const CATEGORIES_FETCH = createFetchType(CATEGORIES_SCOPE);
export const CATEGORIES_FETCH_SUCCESS = createSuccessType(CATEGORIES_SCOPE);
export const CATEGORIES_FETCH_ERROR = createErrorType(CATEGORIES_SCOPE);

export const CATEGORIES_SET = "CATEGORIES_SET";
export const CATEGORIES_SET = createSetType("CATEGORIES_SET");

+ 7
- 1
src/store/actions/categories/categoriesActions.js Zobrazit soubor

@@ -1,4 +1,4 @@
import { CATEGORIES_FETCH, CATEGORIES_SET } from "./categoriesActionConstants";
import { CATEGORIES_FETCH, CATEGORIES_FETCH_ERROR, CATEGORIES_FETCH_SUCCESS, CATEGORIES_SET } from "./categoriesActionConstants";

export const fetchCategories = () => ({
type: CATEGORIES_FETCH
@@ -7,4 +7,10 @@ export const fetchCategories = () => ({
export const setCategories = (payload) => ({
type: CATEGORIES_SET,
payload
})
export const fetchCategoriesSuccess = () => ({
type: CATEGORIES_FETCH_SUCCESS
})
export const fetchCategoriesError = () => ({
type: CATEGORIES_FETCH_ERROR
})

+ 0
- 0
src/store/actions/chat/chatActionConstants.js Zobrazit soubor


Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů

Načítá se…
Zrušit
Uložit