Sfoglia il codice sorgente

Merged with chat-edit-delete

bugfix/520
jovan.cirkovic 3 anni fa
parent
commit
245774af8c
100 ha cambiato i file con 3515 aggiunte e 774 eliminazioni
  1. 10
    1
      src/AppRoutes.js
  2. 6
    0
      src/assets/images/svg/logo-image.svg
  3. 3
    0
      src/assets/images/svg/message.svg
  4. 6
    0
      src/assets/images/svg/package.svg
  5. 12
    0
      src/assets/images/svg/phone.svg
  6. 3
    2
      src/components/Buttons/IconButton/IconButton.js
  7. 5
    1
      src/components/Buttons/PrimaryButton/PrimaryButton.js
  8. 67
    70
      src/components/Cards/ChatCard/ChatCard.js
  9. 55
    19
      src/components/Cards/ChatCard/ChatCard.styled.js
  10. 3
    3
      src/components/Cards/CreateOfferCard/CreateOffer.js
  11. 27
    9
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  12. 68
    55
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  13. 43
    3
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js
  14. 76
    64
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  15. 32
    4
      src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js
  16. 11
    4
      src/components/Cards/FilterCard/FilterCard.js
  17. 13
    4
      src/components/Cards/FilterCard/FilterCard.styled.js
  18. 0
    1
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  19. 0
    1
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  20. 46
    26
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.js
  21. 7
    4
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js
  22. 28
    0
      src/components/Cards/LittleOfferCard/LittleOfferCard.js
  23. 72
    0
      src/components/Cards/LittleOfferCard/LittleOfferCard.styled.js
  24. 34
    0
      src/components/Cards/MessageCard/MessageCard.js
  25. 48
    0
      src/components/Cards/MessageCard/MessageCard.styled.js
  26. 46
    0
      src/components/Cards/MiniChatCard/MiniChatCard.js
  27. 48
    0
      src/components/Cards/MiniChatCard/MiniChatCard.styled.js
  28. 33
    10
      src/components/Cards/OfferCard/OfferCard.js
  29. 18
    7
      src/components/Cards/OfferCard/OfferCard.styled.js
  30. 0
    0
      src/components/Cards/UserReviewsCard/Mockupdata.js
  31. 132
    0
      src/components/Cards/UserReviewsCard/UserReviewsCard.js
  32. 49
    2
      src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js
  33. 38
    32
      src/components/ChatColumn/ChatColumn.js
  34. 55
    11
      src/components/ChatColumn/ChatColumn.styled.js
  35. 126
    0
      src/components/CreateReview/CreateReview.js
  36. 103
    0
      src/components/CreateReview/CreateReview.styled.js
  37. 134
    0
      src/components/CreateReview/FirstStep/FirstStepCreateReview.js
  38. 115
    0
      src/components/CreateReview/FirstStep/FirstStepCreateReview.styled.js
  39. 55
    0
      src/components/CreateReview/SecondStep/SecondStepCreateReview.js
  40. 22
    0
      src/components/CreateReview/SecondStep/SecondStepCreateReview.styled.js
  41. 19
    0
      src/components/CreateReview/ThirdStep/ThirdStepCreateReview.js
  42. 35
    0
      src/components/CreateReview/ThirdStep/ThirdStepCreateReview.styled.js
  43. 84
    0
      src/components/DirectChat/DirectChat.js
  44. 5
    0
      src/components/DirectChat/DirectChat.styled.js
  45. 62
    0
      src/components/DirectChat/DirectChatContent/DirectChatContent.js
  46. 38
    0
      src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js
  47. 45
    0
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js
  48. 75
    0
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js
  49. 79
    0
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.js
  50. 6
    0
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.styled.js
  51. 20
    0
      src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js
  52. 17
    0
      src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.styled.js
  53. 71
    0
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js
  54. 41
    0
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.styled.js
  55. 80
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.js
  56. 8
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js
  57. 20
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js
  58. 25
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.styled.js
  59. 143
    0
      src/components/Header/Drawer/Drawer.js
  60. 156
    0
      src/components/Header/Drawer/Drawer.styled.js
  61. 38
    114
      src/components/Header/Header.js
  62. 11
    10
      src/components/Header/Header.styled.js
  63. 6
    0
      src/components/ImagePicker/ImagePicker.styled.js
  64. 9
    10
      src/components/ItemDetails/Header/Header.js
  65. 19
    23
      src/components/ItemDetails/ItemDetails.js
  66. 20
    12
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js
  67. 16
    9
      src/components/MarketPlace/Header/Header.js
  68. 18
    0
      src/components/MarketPlace/Header/Header.styled.js
  69. 4
    3
      src/components/MarketPlace/MarketPlace.js
  70. 177
    23
      src/components/MarketPlace/Offers/Offers.js
  71. 5
    0
      src/components/Popovers/HeaderPopover/HeaderPopover.styled.js
  72. 18
    11
      src/components/Popovers/MyMessages/MyMessages.js
  73. 5
    0
      src/components/Popovers/MyPosts/MyPosts.js
  74. 1
    1
      src/components/Popovers/MyProfile/MyProfile.js
  75. 21
    2
      src/components/Profile/ProfileOffers/ProfileOffers.js
  76. 13
    10
      src/components/ProfileCard/ProfileCard.js
  77. 1
    1
      src/components/Router/PrivateRoute.js
  78. 27
    0
      src/components/UserReviews/NoReviews/NoReviews.js
  79. 23
    0
      src/components/UserReviews/NoReviews/NoReviews.styled.js
  80. 0
    0
      src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.js
  81. 1
    1
      src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.styled.js
  82. 94
    0
      src/components/UserReviews/UserReviews.js
  83. 131
    0
      src/components/UserReviews/UserReviews.styled.js
  84. 0
    143
      src/components/UserReviewsCard/UserReviewsCard.js
  85. 2
    1
      src/constants/pages.js
  86. 14
    0
      src/enums/reviewEnum.js
  87. 13
    1
      src/hooks/useFilters.js
  88. 6
    7
      src/hooks/useQueryString.js
  89. 14
    11
      src/hooks/useSorting.js
  90. 26
    0
      src/i18n/resources/rs.js
  91. 26
    21
      src/layouts/ChatGridLayout/ChatGridLayout.js
  92. 16
    1
      src/layouts/ChatGridLayout/ChatGridLayout.styled.js
  93. 23
    18
      src/layouts/ChatLayout/ChatLayout.js
  94. 2
    2
      src/layouts/ItemDetailsLayout/ItemDetailsLayout.styled.js
  95. 4
    3
      src/pages/ChatMessages/ChatMessages.js
  96. 0
    1
      src/pages/ChatMessages/ChatMessages.styled.js
  97. 4
    2
      src/pages/ItemDetailsPage/ItemDetailsPageMUI.js
  98. 23
    0
      src/pages/MyOffers/MyOffers.js
  99. 6
    0
      src/pages/MyOffers/MyOffers.styled.js
  100. 0
    0
      src/pages/ProfilePage/ProfilePage.js

+ 10
- 1
src/AppRoutes.js Vedi File

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

@@ -17,6 +18,7 @@ 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";
@@ -33,6 +35,7 @@ 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 (
@@ -49,9 +52,15 @@ const AppRoutes = () => {
<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={HomePage} />
<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>

+ 6
- 0
src/assets/images/svg/logo-image.svg Vedi File

@@ -0,0 +1,6 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="35.7583" width="50.5696" height="50.5696" rx="6.49376" transform="rotate(45 35.7583 0)" fill="#5A3984"/>
<rect x="71.5161" y="35.7578" width="50.5696" height="50.5696" rx="6.49376" transform="rotate(45 71.5161 35.7578)" fill="#FEB005"/>
<circle cx="60.4268" cy="46.3949" r="9.92188" transform="rotate(45 60.4268 46.3949)" fill="#FEB005"/>
<circle cx="46.6221" cy="60.6527" r="9.92188" transform="rotate(45 46.6221 60.6527)" fill="#5A3984"/>
</svg>

+ 3
- 0
src/assets/images/svg/message.svg Vedi File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 13C19 13.5304 18.7893 14.0391 18.4142 14.4142C18.0391 14.7893 17.5304 15 17 15H5L1 19V3C1 2.46957 1.21071 1.96086 1.58579 1.58579C1.96086 1.21071 2.46957 1 3 1H17C17.5304 1 18.0391 1.21071 18.4142 1.58579C18.7893 1.96086 19 2.46957 19 3V13Z" stroke="#4D4D4D" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 6
- 0
src/assets/images/svg/package.svg Vedi File

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.89 1.44917L20.89 5.44917C21.2233 5.61475 21.5037 5.87 21.6998 6.18622C21.8959 6.50244 21.9999 6.86709 22 7.23917V16.7692C21.9999 17.1413 21.8959 17.5059 21.6998 17.8221C21.5037 18.1383 21.2233 18.3936 20.89 18.5592L12.89 22.5592C12.6122 22.6982 12.3058 22.7706 11.995 22.7706C11.6843 22.7706 11.3779 22.6982 11.1 22.5592L3.10005 18.5592C2.76718 18.3915 2.4878 18.134 2.29344 17.816C2.09907 17.4979 1.99745 17.1319 2.00005 16.7592V7.23917C2.00025 6.86709 2.10424 6.50244 2.30033 6.18622C2.49642 5.87 2.77684 5.61475 3.11005 5.44917L11.11 1.44917C11.3866 1.31175 11.6912 1.24023 12 1.24023C12.3089 1.24023 12.6135 1.31175 12.89 1.44917V1.44917Z" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.32031 6.16016L12.0003 11.0002L21.6803 6.16016" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 22.76V11" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 3.5L17 8.5" stroke="#FEB005" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

+ 12
- 0
src/assets/images/svg/phone.svg Vedi File

@@ -0,0 +1,12 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_789_9087)">
<path d="M14.25 0.75L17.25 3.75L14.25 6.75" stroke="#5A3984" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.25 3.75H17.25" stroke="#5A3984" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5001 12.6901V14.9401C16.5009 15.1489 16.4581 15.3557 16.3745 15.5471C16.2908 15.7385 16.168 15.9103 16.0141 16.0515C15.8602 16.1927 15.6785 16.3002 15.4806 16.3671C15.2828 16.434 15.0731 16.4589 14.8651 16.4401C12.5572 16.1893 10.3403 15.4007 8.39257 14.1376C6.58044 12.9861 5.04407 11.4497 3.89257 9.63757C2.62506 7.68098 1.83625 5.45332 1.59007 3.13507C1.57133 2.92767 1.59598 2.71864 1.66245 2.52129C1.72892 2.32394 1.83575 2.14259 1.97615 1.98879C2.11654 1.83499 2.28743 1.7121 2.47792 1.62796C2.6684 1.54382 2.87433 1.50027 3.08257 1.50007H5.33257C5.69655 1.49649 6.04942 1.62538 6.32539 1.86272C6.60137 2.10006 6.78163 2.42966 6.83257 2.79007C6.92754 3.51012 7.10366 4.21712 7.35757 4.89757C7.45848 5.16602 7.48032 5.45776 7.4205 5.73823C7.36069 6.01871 7.22172 6.27616 7.02007 6.48007L6.06757 7.43257C7.13524 9.31023 8.68991 10.8649 10.5676 11.9326L11.5201 10.9801C11.724 10.7784 11.9814 10.6395 12.2619 10.5796C12.5424 10.5198 12.8341 10.5417 13.1026 10.6426C13.783 10.8965 14.49 11.0726 15.2101 11.1676C15.5744 11.219 15.9071 11.4025 16.145 11.6832C16.3828 11.9639 16.5092 12.3223 16.5001 12.6901Z" stroke="#5A3984" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_789_9087">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

+ 3
- 2
src/components/Buttons/IconButton/IconButton.js Vedi File

@@ -4,7 +4,7 @@ import PropTypes from "prop-types";

export const IconButton = (props) => {
return <IconButtonContainer style={props.containerStyle} className={props.className}>
<IconButtonStyled onClick={props.onClick} sx={props.style} iconcolor={props.iconColor}>
<IconButtonStyled disabled={props.disabled} onClick={props.onClick} sx={props.style} iconcolor={props.iconColor}>
{props.children}
</IconButtonStyled>
</IconButtonContainer>
@@ -16,5 +16,6 @@ IconButton.propTypes = {
containerStyle: PropTypes.any,
style: PropTypes.any,
className: PropTypes.string,
iconColor: PropTypes.string
iconColor: PropTypes.string,
disabled: PropTypes.bool,
}

+ 5
- 1
src/components/Buttons/PrimaryButton/PrimaryButton.js Vedi File

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

+ 67
- 70
src/components/Cards/ChatCard/ChatCard.js Vedi File

@@ -2,8 +2,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
MessageIcon,
// OfferImage,
OfferImage,
OfferTitle,
OfferCard,
ChatOffer,
@@ -12,7 +11,7 @@ import {
OfferText,
ChatCardContainer,
Col,
// UserImage,
UserImage,
OfferCardContainer,
UserImgWrapper,
OfferImgWrapper,
@@ -24,14 +23,12 @@ import {
LocationIcon,
OfferCardContainerMobile,
OfferTextMobile,
OfferTitleMobile
OfferTitleMobile,
PhoneIconContainer,
PhoneIcon,
} from "./ChatCard.styled";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";
import selectedTheme from "../../../themes";
import {ReactComponent as DummyImage1} from "../../../assets/images/svg/dummyImages/offer-1.svg";
import { useHistory } from "react-router-dom";
import useScreenDimensions from "../../../hooks/useScreenDimensions";
//import { useSelector } from "react-redux";
@@ -39,84 +36,83 @@ import useScreenDimensions from "../../../hooks/useScreenDimensions";
const ChatCard = (props) => {
const history = useHistory();
const dimensions = useScreenDimensions();
const [isMobile,setIsMobile] = useState(dimensions.width < 600);
const [isMobile, setIsMobile] = useState(dimensions.width < 600);
const chat = props.chat;

// const userId = useSelector(selectUserId);
useEffect(() => {
console.log(isMobile);
const resize = (e) => {
if(e.target.outerWidth < 600 && isMobile)
setIsMobile(false)
else if ( e.target.outerWidth > 600 && !isMobile)
setIsMobile(true)
};
window.addEventListener('resize', resize);
useEffect(() => {
const resize = (e) => {
if (e.target.outerWidth < 600 && isMobile) setIsMobile(false);
else if (e.target.outerWidth > 600 && !isMobile) setIsMobile(true);
};
window.addEventListener("resize", resize);

return () => window.removeEventListener('resize', resize);
},[]);
return () => window.removeEventListener("resize", resize);
}, []);

const routeToItem = (userId) => {
history.push(`/messages/${userId}`);
};
};
return (
<ChatCardContainer onClick={isMobile ? () => routeToItem('12') : () => {} }>
<ChatCardContainer onClick={isMobile ? () => routeToItem(chat?.chat?._id) : () => {}}>
<Col>
<UserImgWrapper>
<UserImage src={chat?.interlocutorData?.image} />
</UserImgWrapper>

<Col>
{/* <UserImage src={DummyImage1} /> */}
<UserImgWrapper><DummyImage1></DummyImage1></UserImgWrapper>
<ChatInfo>
<UserName>Name</UserName>
{/* Only shows on Mobile */}
<OfferCardContainerMobile>
<OfferTextMobile>Proizvod:</OfferTextMobile>
<OfferTitleMobile>Prazne Flase</OfferTitleMobile>
</OfferCardContainerMobile>
{/* ^^^^^ */}
<LastMessage>Last chat details</LastMessage>
<LocationContainer>
<LocationIcon>
<Location height="12px" width="12px" />
</LocationIcon>
<XSText>Beograd, Srbija</XSText>
</LocationContainer>
</ChatInfo>
</Col>
<Line />
<UserName>{chat?.interlocutorData?.name}</UserName>
{/* Only shows on Mobile */}
<OfferCardContainerMobile>
<OfferTextMobile>Proizvod:</OfferTextMobile>
<OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile>
</OfferCardContainerMobile>
{/* ^^^^^ */}
<LastMessage>
{chat?.chat?.messages
? chat?.chat?.messages[chat?.chat?.messages?.length - 1]?.text
: ""}
</LastMessage>
<LocationContainer>
<LocationIcon>
<Location height="12px" width="12px" />
</LocationIcon>
<XSText>{chat?.interlocutorData?.location}</XSText>
</LocationContainer>
</ChatInfo>
</Col>
<Line />

<Col>
<Col mobileDisappear>
<ChatOffer>
<OfferImgWrapper><DummyImage1></DummyImage1></OfferImgWrapper>
{/* <OfferImage/> */}
<OfferCardContainer>
<OfferText>Proizvod:</OfferText>
<OfferTitle>Prazne Flase</OfferTitle>
</OfferCardContainer>
<OfferImgWrapper>
<OfferImage src={chat?.offerData?.firstImage} />
</OfferImgWrapper>
<OfferCardContainer>
<OfferText>Proizvod:</OfferText>
<OfferTitle>{chat?.offerData?.name}</OfferTitle>
</OfferCardContainer>
</ChatOffer>
</Col>
<Commands>
<MessageIcon vertical={props.vertical}>
<Message />
</MessageIcon>
<CheckButton
buttoncolor={selectedTheme.primaryPurple}
textcolor={"white"}
style={{ fontWeight: "600" }}
onClick={() => routeToItem('12')}
>
Pogledaj caskanje
</CheckButton>
</Commands>
</ChatCardContainer>

</Col>
<Commands>
<PhoneIconContainer>
<PhoneIcon />
</PhoneIconContainer>
<CheckButton
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryPurple}
variant={"outlined"}
style={{ fontWeight: "600" }}
onClick={() => routeToItem(chat?.chat?._id)}
>
Pogledaj caskanje
</CheckButton>
</Commands>
</ChatCardContainer>
);
};

ChatCard.propTypes = {
children: PropTypes.node,
_id: PropTypes.string,
title: PropTypes.string,
description: PropTypes.string,
category: PropTypes.string,
@@ -131,6 +127,7 @@ ChatCard.propTypes = {
offer: PropTypes.any,
pinned: PropTypes.bool,
vertical: PropTypes.bool,
chat: PropTypes.any,
};
OfferCard.defaultProps = {
halfwidth: false,

+ 55
- 19
src/components/Cards/ChatCard/ChatCard.styled.js Vedi File

@@ -5,6 +5,7 @@ import { IconButton } from "../../Buttons/IconButton/IconButton";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as Phone } from "../../../assets/images/svg/phone.svg";

export const ChatCardContainer = styled(Container)`
display: flex;
@@ -27,7 +28,8 @@ export const ChatCardContainer = styled(Container)`
position: relative;
justify-content: space-between;
@media (max-width: 550px) {
height: auto;
max-height: 108px;
margin: 0;
${(props) =>
props.vertical &&
`
@@ -42,8 +44,8 @@ export const UserImage = styled.img`
width: 144px;
height: 144px;
@media (max-width: 600px) {
width: 90px;
height: 90px;
width: 72px;
height: 72px;
}
`;

@@ -53,8 +55,9 @@ export const UserImgWrapper = styled(Box)`
width: 144px;
height: 144px;
@media (max-width: 600px) {
width: 90px;
height: 90px;
width: 72px;
height: 72px;
min-width: 80px;
}
`;
export const OfferImgWrapper = styled(Box)`
@@ -62,10 +65,10 @@ export const OfferImgWrapper = styled(Box)`
border-radius: 4px;
width: 72px;
height: 72px;

min-width: 72px;
max-width: 72px;
`;


export const OfferFlexContainer = styled(Container)`
display: flex;
flex-direction: row;
@@ -89,12 +92,11 @@ export const OfferCardContainer = styled(Container)`
max-width: 2000px;
position: relative;
@media (max-width: 550px) {
}
`;

export const OfferCardContainerMobile = styled(Box)`
display: none;
display: none;

@media (max-width: 550px) {
position: relative;
@@ -146,7 +148,7 @@ export const OfferTitleMobile = styled(Typography)`
flex: 1;
color: ${selectedTheme.primaryPurple};
font-weight: 700;
font-size: 18px;
font-size: 12px;
cursor: pointer;
@media (max-width: 550px) {
display: block;
@@ -290,16 +292,26 @@ export const DetailText = styled(Typography)`
export const CheckButton = styled(PrimaryButton)`
width: 180px;
height: 48px;
background-color: ${selectedTheme.primaryPurple};
&:hover button {
background-color: ${selectedTheme.primaryPurple} !important;
&:hover {
background-color: ${selectedTheme.primaryPurple};
color: white !important;
border-radius: 4px;
}
@media (max-width: 1024px) {
width: 150px;
height: 40px;
margin-left: 2vw;
& button {
padding: 0;
font-size: 11px;
}
}
@media (max-width: 600px) {
display: none;
}
transition: 0.2s all;
`;
export const MessageIcon = styled(IconButton)`
export const PhoneIconContainer = styled(IconButton)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor};
@@ -307,8 +319,8 @@ export const MessageIcon = styled(IconButton)`
padding-top: 2px;
text-align: center;
@media (max-width: 600px) {
width: 30px;
height: 30px;
width: 32px;
height: 32px;
top: 16px;
right: 16px;
padding: 0;
@@ -369,6 +381,7 @@ export const ChatOffer = styled(Box)`
display: flex;
flex-direction: row;
align-items: center;
padding-left: 36px;
@media (max-width: 600px) {
display: none;
}
@@ -382,7 +395,7 @@ export const OfferText = styled(Box)`

export const OfferTextMobile = styled(Box)`
font-family: "Open Sans";
font-size: "12px";
font-size: 9px;
color: ${selectedTheme.primaryText};
`;

@@ -410,6 +423,13 @@ export const Col = styled(Box)`
align-items: center;
flex-direction: row;
gap: 18px;
flex: 1;
@media (max-width: 1024px) {
${(props) => props.mobileDisappear && `display: none;`}
}
@media (max-width: 600px) {
${(props) => props.mobileDisappear && `display: none;`}
}
`;

export const UserName = styled(Typography)`
@@ -426,9 +446,16 @@ export const UserName = styled(Typography)`

export const LastMessage = styled(Typography)`
font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
color: ${selectedTheme.primaryDarkTextThird};
line-height: 22px;
font-size: 16px;
max-width: 220px;
flex: 1;
overflow: hidden;
max-height: 66px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
position: relative;
@media (max-width: 600px) {
display: none;
@@ -461,6 +488,7 @@ export const XSText = styled(Typography)`
export const OfferImage = styled.img`
max-width: 72px;
max-height: 72px;
min-width: 72px;
width: 72px;
height: 72px;
border-radius: 4px;
@@ -475,3 +503,11 @@ export const Line = styled(Box)`
display: none;
}
`;
export const PhoneIcon = styled(Phone)`
@media (max-width: 600px) {
width: 14px;
height: 14px;
position: relative;
top: 1px;
}
`;

+ 3
- 3
src/components/Cards/CreateOfferCard/CreateOffer.js Vedi File

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
import { NavLink, useHistory } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import { fetchLogin } from "../../../store/actions/login/loginActions";
@@ -78,7 +78,7 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
historyRouter.location.pathname.length
);
dispatch(fetchProfileOffers(userId));
history.push({
historyRouter.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
@@ -144,7 +144,7 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => {
};

const submitOffer = (values) => {
dispatch(addOffer(values));
dispatch(addOffer({ values, handleApiResponseSuccess }));
};

const submitEditOffer = (id, values) => {

+ 27
- 9
src/components/Cards/CreateOfferCard/CreateOffer.styled.js Vedi File

@@ -7,14 +7,15 @@ import Select from "../../Select/Select";
export const ModalCreateOfferContainer = styled(Box)`
background-color: #fff;
position: fixed;
overflow-y: auto;
${props => props.currentStep === 3 && `overflow-y: auto;`}
max-height: 90vh;
top: ${(props) =>
props.currentStep === 1 ? "calc(50% - 345px);" : "calc(50% - 350px);"};
props.currentStep === 1 ? "calc(50% - 400px);" : "calc(50% - 350px);"};
left: ${(props) =>
props.currentStep !== 3 ? "calc(50% - 310px);" : "calc(50% - 340px);"};
props.currentStep !== 3 ? "calc(50% - 310px);" : "calc(50% - 420px);"};
z-index: 150;
padding: ${(props) => (props.currentStep !== 3 ? "0 120px" : "0 130px")};
overflow-y: auto;
&::-webkit-scrollbar {
width: 5px;
}
@@ -27,13 +28,21 @@ export const ModalCreateOfferContainer = styled(Box)`
scrollbar-width: thin;
scrollbar-color: #ddd;

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

}

@media screen and (max-width: 628px) {
height: 740px;
width: 95%;
left: 10px;
height: 100vh;
max-height: 100vh;
min-height: 90vh;
width: 100vw;
top: 0;
left: 0;
padding: 0 30px;
top: 70px;
}

`;

export const ModalHeader = styled(Box)`
@@ -73,7 +82,7 @@ export const CloseIcon = styled(Box)`
export const CreateOfferContainer = styled(Container)`
margin-top: 0px;
display: flex;
width: ${props => props.currentStep === 3 ? "580px" : "380px"};
width: ${(props) => (props.currentStep === 3 ? "580px" : "380px")};
flex-direction: column;
align-items: center;

@@ -117,7 +126,7 @@ export const CreateOfferDescription = styled(Typography)`
export const CreateOfferFormContainer = styled(Box)`
width: 335px;
height: 700px;
padding-top: 20px;
${props => props.currentStep === 3 && `width: 120%; height: 420px;`}
`;
export const RegisterAltText = styled(Typography)`
font-family: "Poppins";
@@ -143,6 +152,11 @@ export const FieldLabel = styled(Label)`
cursor: auto;
letter-spacing: 0.2px;
}
@media (max-width: 600px) {
& label {
font-size: 9px;
}
}
`;
export const SelectText = styled(Typography)`
font-size: 16px;
@@ -153,6 +167,10 @@ export const SelectField = styled(Select)`
position: relative;
top: 15px;
margin-bottom: 18px;
@media (max-width: 600px) {
height: 40px;
font-size: 12px;
}
& div {
${SelectText} {
font-weight: 600;

+ 68
- 55
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js Vedi File

@@ -6,12 +6,12 @@ import {
DescriptionField,
FieldLabel,
NextButton,
SelectOption,
TitleField,
} from "./FirstPartCreateOffer.styled";
import * as Yup from "yup";
import selectedTheme from "../../../../themes";
import { useTranslation } from "react-i18next";
import Option from "../../../Select/Option/Option";
import { SelectField } from "../CreateOffer.styled";
import { useSelector } from "react-redux";
import useScreenDimensions from "../../../../hooks/useScreenDimensions";
@@ -72,52 +72,55 @@ const FirstPartCreateOffer = (props) => {
};

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

<FieldLabel leftText={t("offer.title")} />
<TitleField
name="nameOfProduct"
placeholder={t("offer.productName")}
italicPlaceholder
margin="normal"
value={formik.values.nameOfProduct}
onChange={formik.handleChange}
error={formik.touched.nameOfProduct && formik.errors.nameOfProduct}
helperText={formik.touched.nameOfProduct && formik.errors.nameOfProduct}
autoFocus
fullWidth
/>

<FieldLabel leftText={t("offer.productDescription")} />
{dimensions.width > 600 ? (
<DescriptionField
name="description"
placeholder={t("offer.description")}
margin="normal"
<FieldLabel leftText={t("offer.title")} />
<TitleField
name="nameOfProduct"
placeholder={t("offer.productName")}
italicPlaceholder
value={formik.values.description}
onChange={formik.handleChange}
error={formik.touched.description && formik.errors.description}
helperText={formik.touched.description && formik.errors.description}
fullWidth
multiline
minRows={4}
height={"100px"}
/>
) : (
<DescriptionField
name="description"
placeholder={t("offer.description")}
margin="normal"
italicPlaceholder
value={formik.values.description}
value={formik.values.nameOfProduct}
onChange={formik.handleChange}
error={formik.touched.description && formik.errors.description}
helperText={formik.touched.description && formik.errors.description}
error={formik.touched.nameOfProduct && formik.errors.nameOfProduct}
helperText={
formik.touched.nameOfProduct && formik.errors.nameOfProduct
}
autoFocus
fullWidth
/>
)}

<FieldLabel leftText={t("offer.productDescription")} />
{dimensions.width > 600 ? (
<DescriptionField
name="description"
placeholder={t("offer.description")}
margin="normal"
italicPlaceholder
value={formik.values.description}
onChange={formik.handleChange}
error={formik.touched.description && formik.errors.description}
helperText={formik.touched.description && formik.errors.description}
fullWidth
multiline
minRows={4}
height={"100px"}
/>
) : (
<DescriptionField
name="description"
placeholder={t("offer.description")}
margin="normal"
italicPlaceholder
value={formik.values.description}
onChange={formik.handleChange}
error={formik.touched.description && formik.errors.description}
helperText={formik.touched.description && formik.errors.description}
fullWidth
/>
)}

<FieldLabel leftText={t("offer.location")} />
<SelectField
@@ -128,12 +131,12 @@ const FirstPartCreateOffer = (props) => {
formik.setFieldValue("location", value.target.value);
}}
>
<Option value="default">{t("offer.choseLocation")}</Option>
<SelectOption value="default">{t("offer.choseLocation")}</SelectOption>
{locations.map((loc) => {
return (
<Option key={loc._if} value={loc.city}>
<SelectOption key={loc._if} value={loc.city}>
{loc.city}
</Option>
</SelectOption>
);
})}
</SelectField>
@@ -147,16 +150,16 @@ const FirstPartCreateOffer = (props) => {
formik.setFieldValue("category", value.target.value);
}}
>
<Option value="default">{t("offer.choseCategory")}</Option>
<SelectOption value="default">{t("offer.choseCategory")}</SelectOption>
{categories.map((cat, i) => {
return (
<Option
<SelectOption
key={i}
value={cat.name}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</Option>
</SelectOption>
);
})}
</SelectField>
@@ -171,16 +174,17 @@ const FirstPartCreateOffer = (props) => {
formik.setFieldValue("subcategory", value.target.value);
}}
>
<Option value="default">{t("offer.choseSubcategory")}</Option>
<SelectOption value="default">{t("offer.choseSubcategory")}</SelectOption>
{subcat &&
subcat.map((sub, i) => {
return (
<Option key={i} value={sub}>
<SelectOption key={i} value={sub}>
{sub}
</Option>
</SelectOption>
);
})}
</SelectField>
</CreateOfferFormContainer>

<NextButton
type="submit"
@@ -189,14 +193,23 @@ const FirstPartCreateOffer = (props) => {
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
onClick={formik.handleSubmit}
disabled={
formik.values?.nameOfProduct?.length === 0 ||
!formik.values?.nameOfProduct ||
formik.values?.description?.length === 0 ||
!formik.values?.description ||
formik.values?.category?.length === 0 ||
!formik.values?.category ||
formik.values?.subcategory?.length === 0 ||
!formik.values?.subcategory ||
formik.values?.location?.length === 0 ||
!formik.values?.location
}
>
{t("offer.continue")}
</NextButton>
</CreateOfferFormContainer>
</>
);
};


+ 43
- 3
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js Vedi File

@@ -3,6 +3,7 @@ import styled from "styled-components";
import selectedTheme from "../../../../themes";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../../../CheckBox/Label";
import Option from "../../../Select/Option/Option";
import { TextField } from "../../../TextFields/TextField/TextField";

export const CreateOfferTitle = styled(Typography)`
@@ -49,17 +50,56 @@ export const FieldLabel = styled(Label)`
cursor: auto;
letter-spacing: 0.2px;
}
@media (max-width: 600px) {
& label {
font-size: 9px;
}
}
`;
export const DescriptionField = styled(TextField)`
margin-bottom: 4px;
@media (max-width: 600px) {
margin-bottom: 0;
& div div input {
font-size: 12px !important;
}
& div {
height: 40px;
}
}
`;
export const TitleField = styled(TextField)`
@media (max-width: 600px) {
margin-bottom: 0;
& div div input {
font-size: 12px !important;
}
& div {
height: 40px;
}
}
`;
export const TitleField = styled(TextField)``;
export const NextButton = styled(PrimaryButton)`
margin-top: 16px;
width: 100%;

@media (min-width: 601px) {
margin-bottom: 18px;
}

@media screen and (max-width: 600px) {
width: 332px;
position: absolute;
bottom: 20px;
bottom: 18px;
height: 44px;
width: calc(100% - 18px);
left: 9px;
& button {
height: 44px;
}
}
`;
export const SelectOption = styled(Option)`
height: 40px !important;
min-height: 40px;
max-height: 40px;
`

+ 76
- 64
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js Vedi File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useMemo, useState, useEffect } from "react";
import PropTypes from "prop-types";
import {
CreateOfferFormContainer,
@@ -9,9 +9,11 @@ import {
} from "./SecondPartCreateOffer.styled";
import ImagePicker from "../../../ImagePicker/ImagePicker";
import { useTranslation, Trans } from "react-i18next";
import Option from "../../../Select/Option/Option";
import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled";
import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled";
import {
NextButton,
SelectOption,
} from "../FirstPart/FirstPartCreateOffer.styled";
import selectedTheme from "../../../../themes";
import { conditionSelectEnum } from "../../../../enums/conditionEnum";
import { useFormik } from "formik";
@@ -61,10 +63,17 @@ const SecondPartCreateOffer = (props) => {
};
const { t } = useTranslation();

let imagesEmpty = 0;
images.forEach((item) => {
if (item === null || item === undefined) imagesEmpty++;
});
const imagesEmpty = useMemo(() => {
let numOfImagesEmpty = 0;
images.forEach((item) => {
if (item === null || item === undefined) numOfImagesEmpty++;
});
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);
@@ -84,21 +93,21 @@ const SecondPartCreateOffer = (props) => {
console.log("slike", images);

return (
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
<Scroller>
{images.map((item, index) => {
console.log(item);
return (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
);
})}
{/* {props.offer === undefined
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
<Scroller>
{images.map((item, index) => {
return (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
);
})}
{/* {props.offer === undefined
? images.map((item, index) => (
<ImagePicker
key={index}
@@ -117,48 +126,51 @@ const SecondPartCreateOffer = (props) => {
showDeleteIcon
/>
))} */}
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
</SupportedFormats>
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.condition
}
onChange={(value) => {
formik.setFieldValue("condition", value.target.value);
}}
>
<Option value="default">{t("offer.choseCondition")}</Option>
{Object.keys(conditionSelectEnum).map((key) => {
var item = conditionSelectEnum[key];
return (
<Option value={item.mainText} key={item.value}>
<SelectText>{item.mainText}</SelectText>
<SelectAltText>{item.altText}</SelectAltText>
</Option>
);
})}
</SelectField>

<NextButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={imagesEmpty === numberOfImages}
disabled={
props.offer === undefined ? imagesEmpty === numberOfImages : false
}
>
{t("offer.continue")}
</NextButton>
</InputButtonContainer>
</CreateOfferFormContainer>
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
</SupportedFormats>
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue={
props.offer === undefined ? "default" : props.offer.condition
}
onChange={(value) => {
formik.setFieldValue("condition", value.target.value);
}}
>
<SelectOption value="default">
{t("offer.choseCondition")}
</SelectOption>
{Object.keys(conditionSelectEnum).map((key) => {
var item = conditionSelectEnum[key];
return (
<SelectOption value={item.mainText} key={item.value}>
<SelectText>{item.mainText}</SelectText>
<SelectAltText>{item.altText}</SelectAltText>
</SelectOption>
);
})}
</SelectField>
</InputButtonContainer>
</CreateOfferFormContainer>
<NextButton
type="submit"
variant="contained"
height="48px"
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
onClick={formik.handleSubmit}
// disabled={imagesEmpty === numberOfImages}
disabled={
props.offer === undefined ? imagesEmpty === numberOfImages : false
}
>
{t("offer.continue")}
</NextButton>
</>
);
};


+ 32
- 4
src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js Vedi File

@@ -1,11 +1,16 @@
import React from "react";
import PropTypes from "prop-types";
import {
CreateOfferFormContainer,
// CreateOfferFormContainer,
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 offer = {
offer: {
category: {
@@ -25,9 +30,32 @@ const ThirdPartCreateOffer = (props) => {
};

return (
<CreateOfferFormContainer component="form" onSubmit={handleSubmit}>
<PreviewCard offer={offer} showBarterButton={false} showPublishButton />
</CreateOfferFormContainer>
<>
<CreateOfferFormContainer currentStep={3} component="form" onSubmit={handleSubmit}>
<PreviewCard
offer={offer}
showBarterButton={false}
showPublishButton={false}
showExchangeButton={false}
hideViews
/>
</CreateOfferFormContainer>
<NextButton
type="submit"
variant="contained"
height="48px"
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
fullWidth
onClick={handleSubmit}
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
>
{t("offer.publish")}
</NextButton>
</>
);
};


+ 11
- 4
src/components/Cards/FilterCard/FilterCard.js Vedi File

@@ -18,12 +18,13 @@ import FilterRadioDropdown from "./FilterDropdown/Radio/FilterRadioDropdown";
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import useFilters from "../../../hooks/useFilters";
import HeaderBack from "../../ItemDetails/Header/Header";

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

useEffect(() => {
if (!filters.selectedCategory || filters.selectedCategory?._id === 0) {
@@ -52,7 +53,12 @@ const FilterCard = (props) => {
};

return (
<FilterCardContainer responsiveOpen={props.responsiveOpen} responsive={props.responsive}>
<FilterCardContainer
responsiveOpen={props.responsiveOpen}
responsive={props.responsive}
myOffers={props.myOffers}
>
{props.myOffers && <HeaderBack />}
<Header>
<Title>{t("filters.title")}</Title>
<Link
@@ -133,7 +139,7 @@ const FilterCard = (props) => {
fontWeight: "600",
fontSize: "12px",
border: "0",
textAlign: "center"
textAlign: "center",
}}
>
ZATVORI
@@ -164,11 +170,12 @@ FilterCard.propTypes = {
responsive: PropTypes.bool,
responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func,
myOffers: PropTypes.bool,
};

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

export default FilterCard;

+ 13
- 4
src/components/Cards/FilterCard/FilterCard.styled.js Vedi File

@@ -7,13 +7,16 @@ export const FilterCardContainer = styled(Box)`
box-sizing: border-box;
border-radius: 0;
border-top-right-radius: 4px;
height: calc(100% - 90px);
height: ${(props) =>
props.myOffers ? `calc(100% - 153px)` : `calc(100% - 90px)`};
padding: 36px;
background-color: white;
width: calc(100% / 12 * 3.5);
left: 0;
bottom: 0;
max-width: 360px;
display: ${(props) => (props.responsive && !props.responsiveOpen ? "none" : "flex")};
display: ${(props) =>
props.responsive && !props.responsiveOpen ? "none" : "flex"};
flex-direction: column;
justify-content: space-between;
background-color: white;
@@ -22,6 +25,12 @@ export const FilterCardContainer = styled(Box)`
z-index: 9;
margin-top: -24px;
transition: all ease-in-out 0.36s;

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

@media (max-width: 900px) {
margin-left: -400px;
${(props) =>
@@ -33,9 +42,9 @@ export const FilterCardContainer = styled(Box)`
width: 100vw;
bottom: 0;
height: calc(100% - 50px);
` : "display: none"};
`
: "display: none"};
transition: all ease-in-out 0.36s;

}
@media (max-width: 600px) {
margin-top: -14px;

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js Vedi File

@@ -45,7 +45,6 @@ const FilterCheckboxDropdown = (props) => {
}, [props.filters])

const handleChange = (item) => {
console.log(item);
if (props.oneValueAllowed) {
props.setItemsSelected([item]);
} else {

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js Vedi File

@@ -40,7 +40,6 @@ const FilterRadioDropdown = (props) => {
}
}, [props.selected])

console.log(props.selected)

const handleClear = () => {
setToSearch("");

+ 46
- 26
src/components/Cards/ItemDetailsCard/ItemDetailsCard.js Vedi File

@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
@@ -16,18 +16,35 @@ import {
OfferDetails,
OfferImage,
Scroller,
PublishButtonContainer,
} from "./ItemDetailsCard.styled";
import { NextButton } from "../CreateOfferCard/FirstPart/FirstPartCreateOffer.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
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 selectedTheme from "../../../themes";
import { useDispatch, useSelector } from "react-redux";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { useHistory } from "react-router-dom";
import { increaseCounter } from "../../../store/actions/counter/counterActions";
import _ from "lodash";
import { selectUserId } from "../../../store/selectors/loginSelectors";

const ItemDetailsCard = (props) => {
const offer = props.offer;
const history = useHistory();
const chats = useSelector(selectLatestChats);
const userId = useSelector(selectUserId);
const dispatch = useDispatch();
const dateCreated = new Date(offer?.offer?._created);

useEffect(() => {
if (offer?.offer?._id) {
_.once(function () {
dispatch(increaseCounter(offer?.offer?._id));
})();
}
}, [offer]);

const dayCreated =
dateCreated.getDate() < 10
? "0" + dateCreated.getDate()
@@ -37,6 +54,20 @@ const ItemDetailsCard = (props) => {
? "0" + (dateCreated.getMonth() + 1)
: dateCreated.getMonth() + 1;
const yearCreated = dateCreated.getFullYear();
const startExchange = () => {
const chatItem = chats.find(
(item) => item.chat.offerId === offer?.offer?._id
);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`);
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id,
});
}
}
};
return (
<ItemDetailsCardContainer
sponsored={props.sponsored.toString()}
@@ -75,12 +106,12 @@ const ItemDetailsCard = (props) => {
</InfoIcon>
<InfoText>{offer?.offer?.condition}</InfoText>
</InfoGroup>
{!props.showPublishButton && (
{!props.hideViews && (
<InfoGroup views>
<InfoIcon color={"black"} component="span" size="12px" last>
<Eye width={"18px"} height={"20px"} />
</InfoIcon>
<InfoText>{offer?.offer?.views?.viewers?.length}</InfoText>
<InfoText>{offer?.offer?.views?.count}</InfoText>
</InfoGroup>
)}
</Info>
@@ -88,7 +119,10 @@ const ItemDetailsCard = (props) => {
{dayCreated}.{monthCreated}.{yearCreated}
</PostDate>
</OfferInfo>
<Details hasScrollBar={!props.showPublishButton}>
<Details
hasScrollBar={!props.showPublishButton}
exchange={props.showExchangeButton}
>
<OfferTitle>{offer?.offer?.name}</OfferTitle>
<Scroller>
{offer?.offer?.images?.map((item) => {
@@ -97,18 +131,19 @@ const ItemDetailsCard = (props) => {
</Scroller>
<OfferDetails>
<OfferDescriptionTitle>Opis:</OfferDescriptionTitle>
<OfferDescriptionText showBarterButton={props.showBarterButton}>
<OfferDescriptionText showBarterButton={props.showExchangeButton}>
{offer?.offer?.description}
</OfferDescriptionText>
</OfferDetails>
</Details>
{!props.halfwidth && !props.showPublishButton ? (
{!props.halfwidth && props.showExchangeButton ? (
<React.Fragment>
<CheckButton
variant={props.sponsored ? "contained" : "outlined"}
buttoncolor={selectedTheme.primaryPurple}
textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple}
style={{ fontWeight: "600" }}
onClick={startExchange}
>
Trampi
</CheckButton>
@@ -116,24 +151,6 @@ const ItemDetailsCard = (props) => {
) : (
<></>
)}
{props.showPublishButton && (
<PublishButtonContainer>
<NextButton
type="submit"
variant="contained"
height="48px"
width="350px"
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
>
OBJAVI
</NextButton>
</PublishButtonContainer>
)}
</ItemDetailsCardContainer>
);
};
@@ -156,6 +173,8 @@ ItemDetailsCard.propTypes = {
halfwidth: PropTypes.bool,
sponsored: PropTypes.bool,
offer: PropTypes.any,
hideViews: PropTypes.bool,
showExchangeButton: PropTypes.bool,
// offer: PropTypes.shape({
// images: PropTypes.any,
// name:PropTypes.string,
@@ -175,6 +194,7 @@ ItemDetailsCard.propTypes = {
ItemDetailsCard.defaultProps = {
halfwidth: false,
sponsored: false,
showExchangeButton: true,
};

export default ItemDetailsCard;

+ 7
- 4
src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js Vedi File

@@ -175,14 +175,14 @@ export const OfferDescriptionText = styled(Box)`
font-size: 16px;
color: ${selectedTheme.primaryDarkText};
line-height: 22px;
max-width: calc(100% - 130px);
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); */
max-width: ${(props) => props.showBarterButton && "calc(100% - 230px)"};
/* overflow: hidden; */
/* display: -webkit-box;
-webkit-line-clamp: 5;
@@ -231,7 +231,7 @@ export const Details = styled(Box)`
display: flex;
flex-direction: column;
gap: 12px;
${(props) => props.hasScrollBar && `max-height: 400px;`}
${(props) => props.hasScrollBar && !props.exchange && `height: 300px;`}
overflow-y: auto;
overflow-x: hidden;
::-webkit-scrollbar {
@@ -244,8 +244,11 @@ export const Details = styled(Box)`

@media screen and (max-width: 600px) {
margin-top: 15px;
${(props) =>
!props.hasScrollBar && props.exchange &&
`
overflow: hidden;
max-height: none;
max-height: none;`}
}
`;
// export const OfferImage = styled.img`

+ 28
- 0
src/components/Cards/LittleOfferCard/LittleOfferCard.js Vedi File

@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import { LittleOfferCardContainer, OfferCategory, OfferCategoryIcon, OfferDetails, OfferImage, OfferName, OfferSwapsIcon, OfferSwapsIconContainer } from './LittleOfferCard.styled'

const LittleOfferCard = (props) => {
return (
<LittleOfferCardContainer>
<OfferImage src={props.image} />
<OfferDetails>
<OfferName>{props.name}</OfferName>
<OfferCategory>
<OfferCategoryIcon />
{props.categoryName}</OfferCategory>
</OfferDetails>
<OfferSwapsIconContainer>
<OfferSwapsIcon />
</OfferSwapsIconContainer>
</LittleOfferCardContainer>
)
}

LittleOfferCard.propTypes = {
image: PropTypes.string,
name: PropTypes.string,
categoryName: PropTypes.string,
}

export default LittleOfferCard

+ 72
- 0
src/components/Cards/LittleOfferCard/LittleOfferCard.styled.js Vedi File

@@ -0,0 +1,72 @@
import { Box, Typography } from "@mui/material"
import styled from "styled-components"
import selectedTheme from "../../../themes"
import {ReactComponent as Category} from "../../../assets/images/svg/category.svg";
import { Icon } from "../../Icon/Icon";
import {ReactComponent as Swaps} from "../../../assets/images/svg/refresh.svg";

export const LittleOfferCardContainer = styled(Box)`
background-color: ${selectedTheme.chatHeaderColor};
border: 1px solid ${selectedTheme.borderNormal};
border-radius: 2px;
min-width: 211px;
max-width: 300px;
height: 90px;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: row;
position: relative;
`
export const OfferImage = styled.img`
width: 54px;
height: 54px;
margin-top: 18px;
margin-left: 18px;
border-radius: 2px;
overflow: hidden;
`
export const OfferDetails = styled(Box)`
display: flex;
flex-direction: column;
margin-top: 25px;
margin-left: 9px;
flex-grow: 1;
`
export const OfferName = styled(Typography)`
font-weight: 600;
font-size: 16px;
font-family: "Open Sans";
color: ${selectedTheme.primaryPurple};
`
export const OfferCategory = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
`
export const OfferCategoryIcon = styled(Category)`
width: 12px;
height: 12px;
position: relative;
top: 1.5px;
right: 2px;
`
export const OfferSwapsIconContainer = styled(Icon)`
width: 40px;
height: 40px;
background-color: ${selectedTheme.primaryPurple};
border-radius: 100%;
position: absolute;
top: -19px;
right: -19px;
& span {
width: 40px;
height: 40px;
}
`
export const OfferSwapsIcon = styled(Swaps)`
width: 18px;
height: 18px;
position: relative;
top: 10px;
`

+ 34
- 0
src/components/Cards/MessageCard/MessageCard.js Vedi File

@@ -0,0 +1,34 @@
import React from "react";
import PropTypes from "prop-types";
import {
MessageCardContainer,
MessageContent,
MessageDate,
MessageText,
ProfileImage,
} from "./MessageCard.styled";
import { formatDateTime } from "../../../util/helpers/dateHelpers";

const MessageCard = (props) => {
const message = props.message;
const dateString = formatDateTime(new Date(message._created))
return (
<MessageCardContainer isMyMessage={props.isMyMessage}>
<ProfileImage src={props.image} />
<MessageContent isMyMessage={props.isMyMessage}>
<MessageText isMyMessage={props.isMyMessage} >{props.message.text}</MessageText>
<MessageDate isMyMessage={props.isMyMessage} >{dateString}</MessageDate>
</MessageContent>
</MessageCardContainer>
);
};

MessageCard.propTypes = {
children: PropTypes.node,
message: PropTypes.any,
image: PropTypes.string,
isMyMessage: PropTypes.bool,
};

export default MessageCard;

+ 48
- 0
src/components/Cards/MessageCard/MessageCard.styled.js Vedi File

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

export const MessageCardContainer = styled(Box)`
display: flex;
flex-direction: ${props => props.isMyMessage ? `row-reverse` : `row`};
margin-bottom: 18px;
`;
export const ProfileImage = styled.img`
width: 54px;
height: 54px;
overflow: hidden;
border-radius: 100%;
@media (max-width: 600px) {
display: none;
}
`;
export const MessageContent = styled(Box)`
background-color: ${(props) =>
props.isMyMessage
? selectedTheme.primaryPurple
: selectedTheme.messageBackground};
border-radius: ${(props) =>
props.isMyMessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"};
padding: 9px;
position: relative;
min-height: 65px;
margin: 0 18px;
min-width: 110px;
@media (max-width: 600px) {
width: 100%;
}
`;
export const MessageText = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
line-height: 22px;
color: ${props => props.isMyMessage ? `white` : selectedTheme.messageText};
`;
export const MessageDate = styled(Typography)`
color: ${props => props.isMyMessage ? selectedTheme.messageMyDate : selectedTheme.messageDate};
font-size: 12px;
font-style: italic;
position: absolute;
bottom: 9px;
left: 9px;
`;

+ 46
- 0
src/components/Cards/MiniChatCard/MiniChatCard.js Vedi File

@@ -0,0 +1,46 @@
import React from "react";
import PropTypes from "prop-types";
import {
MiniChatCardContainer,
ProfileDetails,
ProfileImage,
ProfileName,
ProfileProduct,
ProfileProductName,
} from "./MiniChatCard.styled";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";

const MiniChatCard = (props) => {
const { t } = useTranslation();
const history = useHistory();
const changeChat = () => {
// if (!props.selected) {
history.push(`/messages/${props?.chat?.chat?._id}`)
// }
}
return (
<MiniChatCardContainer selected={props.selected} onClick={changeChat}>
<ProfileImage src={props?.chat?.interlocutorData?.image} />
<ProfileDetails>
<ProfileName selected={props.selected}>
{props?.chat?.interlocutorData?.name}
</ProfileName>
<ProfileProduct selected={props.selected}>
{t("messages.cardProduct")}
</ProfileProduct>
<ProfileProductName selected={props.selected}>
{props?.chat?.offerData?.name}
</ProfileProductName>
</ProfileDetails>
</MiniChatCardContainer>
);
};

MiniChatCard.propTypes = {
children: PropTypes.node,
chat: PropTypes.any,
selected: PropTypes.bool,
};

export default MiniChatCard;

+ 48
- 0
src/components/Cards/MiniChatCard/MiniChatCard.styled.js Vedi File

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

export const MiniChatCardContainer = styled(Box)`
background-color: ${props => props.selected ? selectedTheme.primaryPurple : "white"};
border-radius: 4px;
display: flex;
flex-direction: row;
height: 108px;
margin-bottom: 18px;
padding: 18px;
cursor: pointer;
`;
export const ProfileImage = styled.img`
width: 72px;
height: 72px;
border-radius: 100%;
overflow: hidden;
`;
export const ProfileDetails = styled(Box)`
display: flex;
flex-direction: column;
margin-left: 18px;
padding-top: 7px;
`;
export const ProfileName = styled(Typography)`
font-size: 16px;
font-weight: 600;
font-family: "Open Sans";
color: ${props => props.selected ? selectedTheme.primaryYellow : selectedTheme.primaryPurple};
`;
export const ProfileProduct = styled(Typography)`
margin-top: 9px;
font-size: 9px;
color: ${props => props.selected ? "white" : selectedTheme.primaryDarkTextThird};
font-family: "Open Sans";
line-height: 10px;
margin-left: 1px;
`;
export const ProfileProductName = styled(Typography)`
font-size: 12px;
font-weight: ${props => props.selected ? "400" : "600"};
color: ${props => props.selected ? "white" : selectedTheme.primaryDarkTextThird};
font-family: "Open Sans";
line-height: 14px;
margin-left: 1px;
`;

+ 33
- 10
src/components/Cards/OfferCard/OfferCard.js Vedi File

@@ -27,6 +27,8 @@ import {
OfferViews,
RemoveIcon,
RemoveIconContainer,
StarIcon,
StarIconContainer,
} from "./OfferCard.styled";
import DeleteOffer from "./DeleteOffer";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
@@ -43,6 +45,14 @@ const OfferCard = (props) => {
const routeToItem = (itemId) => {
history.push(`/proizvodi/${itemId}`);
};
const messageUser = () => {
props.messageUser(props.offer);
};
const makeReview = () => {
if (!props.disabledReviews) {
props.makeReview(props.offer);
}
};

const closeModalHandler = () => {
setDeleteOfferModal(false);
@@ -58,15 +68,14 @@ const OfferCard = (props) => {
document.body.style.overflow = "auto";
}

console.log(props.offer);
return (
<React.Fragment>
<OfferCardContainer
vertical={props.vertical}
sponsored={
props.pinned !== undefined
? props.pinned.toString()
: props?.offer?.pinned.toString()
props?.pinned !== undefined
? props?.pinned?.toString()
: props?.offer?.pinned?.toString()
}
halfwidth={props.halfwidth ? 1 : 0}
>
@@ -79,7 +88,7 @@ const OfferCard = (props) => {
<OfferFlexContainer vertical={props.vertical}>
<OfferImageContainer vertical={props.vertical}>
<OfferImage
src={props?.offer?.images[0]}
src={props?.offer?.images ? props?.offer?.images[0] : ""}
vertical={props.vertical}
></OfferImage>
</OfferImageContainer>
@@ -103,14 +112,14 @@ const OfferCard = (props) => {
<DetailIcon color="black" component="span" size="16px">
<Category width={"14px"} />
</DetailIcon>
<DetailText>{props?.offer?.category.name}</DetailText>
<DetailText>{props?.offer?.category?.name}</DetailText>
</OfferCategory>
<OfferViews vertical={props.vertical}>
{props.dontShowViews ? (<></>) : (<OfferViews vertical={props.vertical}>
<DetailIcon color="black" component="span" size="16px">
<EyeIcon />
</DetailIcon>
<DetailText>{props?.offer?.views?.viewers?.length}</DetailText>
</OfferViews>
<DetailText>{props?.offer?.views?.count}</DetailText>
</OfferViews>)}
</OfferDetails>
</OfferInfo>
{!props.halfwidth ? (
@@ -154,8 +163,15 @@ const OfferCard = (props) => {
<EditIcon />
</EditIconContainer>
</>
) : props.aboveChat ? (
<StarIconContainer
disabled={props.disabledReviews}
onClick={makeReview}
>
<StarIcon disabled={props.disabledReviews} />
</StarIconContainer>
) : (
<MessageIcon vertical={props.vertical}>
<MessageIcon vertical={props.vertical} onClick={messageUser}>
<Message />
</MessageIcon>
)}
@@ -196,10 +212,17 @@ OfferCard.propTypes = {
pinned: PropTypes.bool,
vertical: PropTypes.bool,
isMyOffer: PropTypes.bool,
aboveChat: PropTypes.bool,
disabledReviews: PropTypes.bool,
messageUser: PropTypes.func,
makeReview: PropTypes.func,
dontShowViews: PropTypes.bool,
};
OfferCard.defaultProps = {
halfwidth: false,
sponsored: false,
messageUser: () => {},
makeReview: () => {},
};

export default OfferCard;

+ 18
- 7
src/components/Cards/OfferCard/OfferCard.styled.js Vedi File

@@ -7,6 +7,7 @@ import { Icon } from "../../Icon/Icon";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg";
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;
@@ -331,12 +332,22 @@ export const RemoveIconContainer = styled(MessageIcon)`
export const RemoveIcon = styled(Remove)``;
export const EditIconContainer = styled(MessageIcon)`
right: 70px;

@media screen and (max-width: 600px) {
position: absolute;
display: block;
right: 20px;
top: 60%;
}
`;
export const EditIcon = styled(Edit)``;
export const StarIconContainer = styled(MessageIcon)`
opacity: ${props => props.disabled ? "0.4" : "1"};
${props => props.disabled && `
cursor: initial;
& button {
cursor: initial;
}
`}
`;
export const StarIcon = styled(Star)`
& path {
stroke: ${(props) =>
props.disabled
? selectedTheme.primaryPurpleDisabled
: selectedTheme.primaryPurple};
}
`;

src/components/UserReviewsCard/Mockupdata.js → src/components/Cards/UserReviewsCard/Mockupdata.js Vedi File


+ 132
- 0
src/components/Cards/UserReviewsCard/UserReviewsCard.js Vedi File

@@ -0,0 +1,132 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import {
ProfileImage,
ProfileImageContainer,
ProfileName,
ReviewContainer,
ReviewDetails,
ReviewDetailsText,
ReviewDetailsValue,
ReviewQuote,
ReviewQuoteBox,
ReviewQuoteText,
ThumbBox,
ThumbDown,
ThumbUp,
} from "./UserReviewsCard.styled";

import { ListItem } from "@mui/material";
import selectedTheme from "../../../themes";
import { useTranslation } from "react-i18next";
import { reviewEnum } from "../../../enums/reviewEnum";
// import { useDispatch } from "react-redux";
// import { fetchProfile } from "../../../store/actions/profile/profileActions";

const UserReviewsCard = (props) => {
const { t } = useTranslation();

// const dispatch = useDispatch();

// useEffect(() => {
// if (props.review?.userId) {
// dispatch(fetchProfile(props.review.userId));
// }
// }, [props.review?.userId])
console.log(props);

const review = useMemo(() => {
if (props.givingReview) {
return {
...props.review
}
}
let isSuccessfulSwap = "DA";
if (props.review.succeeded === "failed") isSuccessfulSwap = "NE";
let isGoodCommunication = "DA";
if (props.review.communication === "could be better") isGoodCommunication = "MOŽE BOLJE";
if (props.review.communication === "no") isGoodCommunication = "NE";
return {
name: props.review.companyName,
image: props.review.image,
isGoodCommunication,
isSuccessfulSwap,
quote: props?.review?.message
}
}, [props.review]);

const isGood = useMemo(() => {
if (
review?.isGoodCommunication === reviewEnum.NO.mainText ||
review?.isSuccessfulSwap === reviewEnum.NO.mainText
) {
return false;
}
return true;
}, [review]);

return (
<ReviewContainer key={review?.image}>
<ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}>
<ProfileImageContainer>
<ProfileImage alt={review?.name} src={review?.image} />
</ProfileImageContainer>
<ProfileName sx={{ color: selectedTheme.primaryPurple }}>
<b>{review?.name}</b>
</ProfileName>
</ListItem>
<ReviewQuote
container
direction="row"
justifyContent="start"
alignItems="center"
spacing={2}
sx={{ pl: 2, py: 2 }}
>
<ThumbBox item>
{isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />}
</ThumbBox>
<ReviewQuoteBox item>
<ReviewQuoteText
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
&quot;{review?.quote}&quot;
</ReviewQuoteText>
</ReviewQuoteBox>
</ReviewQuote>
<ReviewDetails sx={{ pl: 2, pb: 2 }}>
<ReviewDetailsText variant="body2" sx={{ display: "block" }}>
{t("reviews.isCorrectCommunication") + ": "}
<ReviewDetailsValue>
{review?.isGoodCommunication?.toUpperCase()}
</ReviewDetailsValue>
</ReviewDetailsText>
<ReviewDetailsText variant="body2" sx={{ display: "block" }}>
{t("reviews.hasExchangeSucceed") + ": "}
<ReviewDetailsValue>
{review?.isSuccessfulSwap?.toUpperCase()}
</ReviewDetailsValue>
</ReviewDetailsText>
</ReviewDetails>
</ReviewContainer>
);
};

UserReviewsCard.propTypes = {
children: PropTypes.node,
heading: PropTypes.string,
isProfileReviews: PropTypes.bool,
profileReviews: PropTypes.any,
className: PropTypes.string,
review: PropTypes.any,
givingReview: PropTypes.bool,
};
UserReviewsCard.defaultProps = {
isProfileReviews: false,
profileReviews: [],
};

export default UserReviewsCard;

src/components/UserReviewsCard/UserReviewsCard.styled.js → src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js Vedi File

@@ -2,13 +2,14 @@ import styled from "styled-components";
import { List, Box, Typography, Grid } from "@mui/material";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import ThumbDownIcon from "@mui/icons-material/ThumbDown";
import selectedTheme from "../../themes";
import selectedTheme from "../../../themes";

export const ReviewsBox = styled(Box)`
width: 100%;
height: calc(100% - 90px);
max-height: 100vh;
@media (max-width: 1200px) {
padding: 0 50px;
padding: 0;
}
@media (max-width: 600px) {
position: relative;
@@ -82,3 +83,49 @@ export const NoReviewsAltText = styled(Typography)`
width: 100%;
margin-bottom: 36px;
`
export const ProfileImage = styled.img`
width: 54px;
height: 54px;
border-radius: 100%;
`
export const ProfileImageContainer = styled(Box)`
width: 54px;
height: 54px;
border-radius: 100%;
margin-right: 14px;
`
export const ReviewQuote = styled(Grid)`
position: relative;
left: 8px;
`
export const ThumbBox = styled(Grid)`
max-width: 20px;
`
export const ReviewQuoteBox = styled(Grid)`
`
export const ReviewQuoteText = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
`
export const ReviewDetails = styled(Grid)`
`
export const ReviewDetailsText = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
font-style: italic;
letter-spacing: 0.02em;
`
export const ReviewDetailsValue = styled(Typography)`
color: ${selectedTheme.primaryPurple};
font-style: normal;
font-weight: 600;
`
export const ProfileName = styled(Typography)`
font-weight: 600;
font-size: 16px;
font-family: "Open Sans";
`
export const ReviewContainer = styled(Box)`
`

+ 38
- 32
src/components/ChatColumn/ChatColumn.js Vedi File

@@ -1,11 +1,12 @@
import React, {useState, useEffect} from "react";
import React, { useState, useEffect } from "react";
import ChatCard from "../Cards/ChatCard/ChatCard";
import Header from "../ItemDetails/Header/Header";
import { HeaderSelect, SelectOption } from "../MarketPlace/Header/Header.styled";
import {
ChatColumnContainer,
HeaderBack,
HeaderSelect,
ListContainer,
ListHeader,
SelectOption,
TitleSortContainer,
} from "./ChatColumn.styled";
import { sortEnum } from "../../enums/sortEnum";
@@ -15,48 +16,54 @@ import { IconStyled } from "../Icon/Icon.styled";
import { Grid } from "@mui/material";
import MailOutlineIcon from "@mui/icons-material/MailOutline";
import { HeaderTitle } from "../ProfileCard/ProfileCard.styled";
//import { useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { selectLatestChats } from "../../store/selectors/chatSelectors";
import { fetchChats } from "../../store/actions/chat/chatActions";

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

export const ChatColumn = () => {
const dispatch = useDispatch();
const sorting = useSorting();
const { t } = useTranslation();
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const chats = useSelector(selectLatestChats);

const sorting = useSorting();
//const { t } = useTranslation();
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);

useEffect(() => {
setSortOption(sorting.selectedSortOption);
}, [sorting.selectedSortOption]);
useEffect(() => {
dispatch(fetchChats());
}, [])

useEffect(() => {
setSortOption(sorting.selectedSortOption);
}, [sorting.selectedSortOption]);

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

return (
<ChatColumnContainer>
<Header />
<HeaderBack />
<TitleSortContainer>
<Grid
<Grid
container
direction="row"
justifyContent="start"
alignItems="center"
>
<MailOutlineIcon color="action" sx={{ mr: 0.9 }} />
<HeaderTitle>Moj Profil</HeaderTitle>
<HeaderTitle>{t("header.myMessages")}</HeaderTitle>
</Grid>
<HeaderSelect
value={sortOption?.value ? sortOption.value : sortEnum.INITIAL.value}
@@ -77,10 +84,9 @@ export const ChatColumn = () => {
</TitleSortContainer>
<ListHeader enableSort={true}></ListHeader>
<ListContainer>
<ChatCard></ChatCard>
<ChatCard></ChatCard>
<ChatCard></ChatCard>
<ChatCard></ChatCard>
{chats.map((item, index) => (
<ChatCard key={index} chat={item} />
))}
</ListContainer>
</ChatColumnContainer>
);

+ 55
- 11
src/components/ChatColumn/ChatColumn.styled.js Vedi File

@@ -1,19 +1,27 @@
import { Box } from "@mui/material";
import { Container } from "@mui/system";
import styled from "styled-components";
import selectedTheme from "../../themes";
import Header from "../ItemDetails/Header/Header";
import Option from "../Select/Option/Option";
import Select from "../Select/Select";

export const ChatColumnContainer = styled(Container)`

`;
margin-bottom: 40px;
`;

export const ListContainer = styled(Box)`
display: flex;
flex-direction: column;
gap:12px;
display: flex;
flex-direction: column;
gap: 12px;
@media (max-width: 600px) {
gap: 18px;
margin-top: 20px;
}
`;

export const ListHeader = styled(Box)`
${(props) =>
${(props) =>
props.vertical &&
`
position: absolute;
@@ -22,9 +30,45 @@ export const ListHeader = styled(Box)`
`;

export const TitleSortContainer = styled(Box)`
margin-top: 26px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 26px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
`;
export const HeaderSelect = styled(Select)`
width: 210px;
height: 35px;
font-family: "Open Sans";
margin-top: 3px;
font-weight: 400;
position: relative;
left: -5px;
& div:first-child {
padding-left: 8px;
}

@media (max-width: 650px) {
width: 144px;
height: 30px;
font-size: 14px;
background-color: white;
& fieldset {
border: 1px solid ${selectedTheme.borderNormal} !important;
}
}
`;
export const SelectOption = styled(Option)`
@media (max-width: 600px) {
height: 20px !important;
min-height: 35px;
margin: 2px;
}
`;
export const HeaderBack = styled(Header)`
@media (max-width: 600px) {
margin-top: 0;
position: relative;
top: -12px;
}
`;

+ 126
- 0
src/components/CreateReview/CreateReview.js Vedi File

@@ -0,0 +1,126 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import {
ArrowBackIcon,
BackIcon,
CloseButton,
CloseIcon,
CreateReviewContainer,
} from "./CreateReview.styled";
import FirstStepCreateReview from "./FirstStep/FirstStepCreateReview";
import SecondStepCreateReview from "./SecondStep/SecondStepCreateReview";
import { useDispatch, useSelector } from "react-redux";
import { giveReview } from "../../store/actions/review/reviewActions";
import { selectUserId } from "../../store/selectors/loginSelectors";
import { reviewEnum } from "../../enums/reviewEnum";
import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview";

const CreateReview = (props) => {
const offer = props.offer;
console.log("props aaa: ", props);
const [informations, setInformations] = useState({});
const [currentStep, setCurrentStep] = useState(1);
const dispatch = useDispatch();
const userId = useSelector(selectUserId);
const closeModal = () => {
props.closeModal();
};
const handleApiResponseSuccess = () => {
props.handleGiveReviewSuccess();
};
const submitForm = () => {
let communication;
if (informations.correctCommunication === reviewEnum.YES.mainText)
communication = "yes";
if (informations.correctCommunication === reviewEnum.NO.mainText)
communication = "no";
if (informations.correctCommunication === reviewEnum.NOT_BAD.mainText)
communication = "could be better";
let succeeded;
succeeded = "failed";
if (informations.exchangeSucceed === reviewEnum.YES.mainText)
succeeded = "succeeded";
dispatch(
giveReview({
review: {
exchangeId: props.exchange._id,
userId: userId,
succeeded,
communication,
message: informations.comment,
},
handleApiResponseSuccess,
})
);
};
const goToNextStep = (newInformations) => {
setInformations((prevInformations) => {
console.log({
...prevInformations,
...newInformations,
});
return {
...prevInformations,
...newInformations,
};
});
if (currentStep === 3) {
closeModal();
} else {
if (currentStep === 2) {
submitForm();
}
setCurrentStep((prevStep) => prevStep + 1);
}
};
const goToPrevStep = () => {
if (currentStep === 2) {
setInformations({});
setCurrentStep(1);
}
if (currentStep === 3) {
setCurrentStep(2);
}
};
return (
<CreateReviewContainer currentStep={currentStep}>
<CloseButton onClick={closeModal}>
<CloseIcon />
</CloseButton>
{currentStep === 2 ? (
<BackIcon onClick={goToPrevStep}>
<ArrowBackIcon />
</BackIcon>
) : (
""
)}
{currentStep === 1 && (
<FirstStepCreateReview
offer={offer}
interlocutor={props.interlocutor}
goToNextStep={goToNextStep}
/>
)}
{currentStep === 2 && (
<SecondStepCreateReview
review={informations}
offer={offer}
interlocutor={props.interlocutor}
goToNextStep={goToNextStep}
/>
)}
{currentStep === 3 && <ThirdStepCreateReview />}
</CreateReviewContainer>
);
};

CreateReview.propTypes = {
children: PropTypes.node,
offer: PropTypes.any,
interlocutor: PropTypes.any,
closeModal: PropTypes.func,
exchange: PropTypes.any,
handleGiveReviewSuccess: PropTypes.func,
};

export default CreateReview;

+ 103
- 0
src/components/CreateReview/CreateReview.styled.js Vedi File

@@ -0,0 +1,103 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import IconButton from "../IconButton/IconButton";
import { ReactComponent as Close } from "../../assets/images/svg/close-modal.svg";
import { ReactComponent as ArrowBack } from "../../assets/images/svg/arrow-back.svg";
import selectedTheme from "../../themes";

export const CreateReviewContainer = styled(Box)`
background-color: #fff;
position: fixed;
overflow-y: auto;
max-height: 90vh;
width: 600px;
overflow-x: hidden;
box-sizing: border-box;
top: ${(props) =>
props.currentStep === 1 ? "calc(50% - 400px);" : "calc(50% - 320px);"};
left: calc(50% - 300px);
z-index: 150;
padding: ${(props) => (props.currentStep !== 3 ? "0 120px" : "0 130px")};
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: #ddd;
}
&::-webkit-scrollbar-thumb {
background: #777;
}
scrollbar-width: thin;
scrollbar-color: #ddd;

@media screen and (max-width: 628px) {
max-height: 100vh;
height: 100vh;
width: 100vw;
max-width: 100vw;
left: 0;
top: 0;
padding: 0 30px;
}
`;
export const NextButton = styled(PrimaryButton)`
@media (max-width: 600px) {
height: 42px;
position: absolute;
bottom: 15px;
width: calc(100% - 48px);
left: 0;
margin-left: 24px;
}
`;
export const CloseButton = styled(IconButton)`
position: absolute;
top: 36px;
right: 36px;
@media (max-width: 600px) {
top: 24px;
right: 24px;
}
`;
export const CloseIcon = styled(Close)`
width: 24px;
height: 24px;
@media (max-width: 600px) {
width: 18px;
height: 18px;
}
`;
export const BackIcon = styled(Box)`
cursor: pointer;
position: absolute;

top: 36px;
left: 36px;

@media (max-width: 600px) {
top: 24px;
left: 24px;
width: 18px;
height: 18px;
}
`;
export const ArrowBackIcon = styled(ArrowBack)`
@media (max-width: 600px) {
width: 18px;
height: 18px;
}
`;
export const CreateReviewTitle = styled(Typography)`
width: 100%;
text-align: center;
color: ${selectedTheme.primaryPurple};
font-family: "Open Sans";
font-size: 24px;
font-weight: 700;
padding-bottom: 36px;
@media (max-width: 600px) {
font-size: 18px;
margin-bottom: 24px;
}
`;

+ 134
- 0
src/components/CreateReview/FirstStep/FirstStepCreateReview.js Vedi File

@@ -0,0 +1,134 @@
import React from "react";
import PropTypes from "prop-types";
import {
FirstStepCreateReviewContainer,
CreateReviewTitle,
ProfileImage,
ProfileImageContainer,
ProfileName,
FieldLabel,
SelectField,
SelectOption,
CommentField,
} from "./FirstStepCreateReview.styled";
import { useTranslation } from "react-i18next";
import LittleOfferCard from "../../Cards/LittleOfferCard/LittleOfferCard";
import { reviewEnum } from "../../../enums/reviewEnum";
import { NextButton } from "../CreateReview.styled";
import selectedTheme from "../../../themes";
import { useFormik } from "formik";
import * as Yup from "yup";
import useScreenDimensions from "../../../hooks/useScreenDimensions";

// const selectFieldValidation = Yup.string().oneOf(Object.keys(reviewEnum).map(property => reviewEnum[property].mainText));

const FirstStepCreateReview = (props) => {
const offer = props.offer;
const interlocutor = props.interlocutor;
const dimensions = useScreenDimensions();
const { t } = useTranslation();
const handleSubmit = (values) => {
props.goToNextStep(values);
};

const formik = useFormik({
initialValues: {
exchangeSucceed: reviewEnum.YES.mainText,
correctCommunication: reviewEnum.YES.mainText,
comment: "",
},
validationSchema: Yup.object().shape({
comment: Yup.string().min(5, t("reviews.commentError")),
}),
onSubmit: handleSubmit,
validateOnBlur: true,
enableReinitialize: true,
});

return (
<FirstStepCreateReviewContainer
component="form"
onSubmit={formik.handleSubmit}
>
<CreateReviewTitle>{t("reviews.modalTitle")}</CreateReviewTitle>
<ProfileImageContainer>
<ProfileImage src={interlocutor.image} />
</ProfileImageContainer>
<ProfileName>{interlocutor.name}</ProfileName>
<LittleOfferCard
image={offer?.images[0]}
name={offer?.name}
categoryName={offer?.category?.name}
/>
<FieldLabel
leftText={t("reviews.isCorrectCommunication").toUpperCase()}
/>
<SelectField
defaultValue={reviewEnum.YES}
onChange={(event) =>
formik.setFieldValue(
"correctCommunication",
event.target.value.mainText
)
}
>
{Object.keys(reviewEnum).map((property) => (
<SelectOption
key={reviewEnum[property].value}
value={reviewEnum[property]}
>
{reviewEnum[property].mainText}
</SelectOption>
))}
</SelectField>

<FieldLabel leftText={t("reviews.hasExchangeSucceed").toUpperCase()} />
<SelectField
defaultValue={reviewEnum.YES}
onChange={(event) =>
formik.setFieldValue("exchangeSucceed", event.target.value.mainText)
}
>
{Object.keys(reviewEnum).map((property) => (
<SelectOption
key={reviewEnum[property].value}
value={reviewEnum[property]}
>
{reviewEnum[property].mainText}
</SelectOption>
))}
</SelectField>

<FieldLabel leftText={t("reviews.comment")} />
<CommentField
fullWidth
multiline
minRows={4}
value={formik.values.comment}
name="comment"
onChange={formik.handleChange}
height={dimensions.width < 600 ? "64px" : "100px"}
/>

<NextButton
variant="contained"
buttoncolor={selectedTheme.primaryPurple}
fullWidth
height="48px"
type="submit"
disabled={formik.values.comment?.length < 5}
>
{t("common.continue")}
</NextButton>
</FirstStepCreateReviewContainer>
);
};

FirstStepCreateReview.propTypes = {
children: PropTypes.node,
offer: PropTypes.any,
interlocutor: PropTypes.any,
goToNextStep: PropTypes.func,
};

export default FirstStepCreateReview;

+ 115
- 0
src/components/CreateReview/FirstStep/FirstStepCreateReview.styled.js Vedi File

@@ -0,0 +1,115 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { Label } from "../../CheckBox/Label";
import Option from "../../Select/Option/Option";
import Select from "../../Select/Select";
import { TextField } from "../../TextFields/TextField/TextField";

export const FirstStepCreateReviewContainer = styled(Box)`
text-align: center;
padding: 36px;
@media (max-width: 600px) {
padding: 18px;
}
`;
export const CreateReviewTitle = styled(Typography)`
width: 100%;
text-align: center;
color: ${selectedTheme.primaryPurple};
font-family: "Open Sans";
font-size: 24px;
font-weight: 700;
padding-bottom: 36px;
@media (max-width: 600px) {
font-size: 18px;
padding-bottom: 24px;
}
`;
export const ProfileImageContainer = styled(Box)`
width: 108px;
height: 108px;
overflow: hidden;
border-radius: 100%;
margin-left: auto;
margin-right: auto;
margin-bottom: 14px;
`;
export const ProfileImage = styled.img`
width: 108px;
height: 108px;
overflow: hidden;
border-radius: 100%;
`;
export const ProfileName = styled(CreateReviewTitle)`
padding-top: 0;
padding-bottom: 14px;
`;
export const FieldLabel = styled(Label)`
position: relative;
bottom: -14px;
width: 100%;
& label {
font-size: 12px;
font-weight: 600;
line-height: 20px;
color: ${selectedTheme.primaryGrayText};
cursor: auto;
letter-spacing: 0.2px;
text-align: left;
}
@media (max-width: 600px) {
& label {
font-size: 10px;
}
}
`;
export const SelectField = styled(Select)`
position: relative;
top: 15px;
margin-bottom: 18px;
height: 48px;
text-align: left;
@media (max-width: 600px) {
height: 33px;
font-size: 14px;
margin-bottom: 12px;
}
`;
export const SelectOption = styled(Option)`
font-family: "Open Sans";
font-size: 16px;
text-align: left;
@media (max-width: 600px) {
font-size: 12px;
height: 35px !important;
min-height: 35px;
}
`;
export const CommentField = styled(TextField)`
& * {
font-family: "Open Sans";
font-size: 16px;
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: #ddd;
}
&::-webkit-scrollbar-thumb {
background: #777;
}
scrollbar-width: thin;
scrollbar-color: #ddd;
@media (max-width: 600px) {
& * {
font-size: 12px !important;
/* line-height: 16px; */
}
& div {
padding: 4px !important;
padding-left: 8px !important;
}
}
}
`;

+ 55
- 0
src/components/CreateReview/SecondStep/SecondStepCreateReview.js Vedi File

@@ -0,0 +1,55 @@
import React from "react";
import PropTypes from "prop-types";
import {
ReviewCard,
SecondStepCreateReviewContainer,
} from "./SecondStepCreateReview.styled";
import { CreateReviewTitle, NextButton } from "../CreateReview.styled";
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";

const SecondStepCreateReview = (props) => {
console.log(props);
const {t} = useTranslation();

const goToNextStep = () => {
props.goToNextStep();
}

return (
<SecondStepCreateReviewContainer>
<CreateReviewTitle>{t("reviews.modalTitle")}</CreateReviewTitle>
<ReviewCard
givingReview
profileReviews={
[{
name: props.interlocutor?.name,
image: props.interlocutor?.image,
isGoodCommunication: props.review?.correctCommunication,
isSuccessfulSwap: props.review?.exchangeSucceed,
quote: props.review.comment,
}]
}
/>
<NextButton
variant="contained"
buttoncolor={selectedTheme.primaryPurple}
fullWidth
height="48px"
onClick={goToNextStep}
>
{t("reviews.leaveComment")}
</NextButton>
</SecondStepCreateReviewContainer>
);
};

SecondStepCreateReview.propTypes = {
children: PropTypes.node,
review: PropTypes.any,
offer: PropTypes.any,
interlocutor: PropTypes.any,
goToNextStep: PropTypes.func,
};

export default SecondStepCreateReview;

+ 22
- 0
src/components/CreateReview/SecondStep/SecondStepCreateReview.styled.js Vedi File

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

export const SecondStepCreateReviewContainer = styled(Box)`
padding: 36px;
@media (max-width: 600px) {
padding: 18px;
}
`
export const ReviewCard = styled(UserReviews)`
margin-bottom: 36px;
margin-top: 18px;
& ul {
background-color: ${selectedTheme.chatHeaderColor};
padding: 0 14px;
}
& ul li {
margin: 0;
}
`

+ 19
- 0
src/components/CreateReview/ThirdStep/ThirdStepCreateReview.js Vedi File

@@ -0,0 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { AltTitle, LogoImage, MainTitle, ThirdStepCreateReviewContainer } from './ThirdStepCreateReview.styled'

const ThirdStepCreateReview = () => {
return (
<ThirdStepCreateReviewContainer>
<LogoImage />
<MainTitle>Hvala Vam </MainTitle>
<AltTitle >na izdvojenom vremenu i datoj oceni!</AltTitle>
</ThirdStepCreateReviewContainer>
)
}

ThirdStepCreateReview.propTypes = {
children: PropTypes.node,
}

export default ThirdStepCreateReview

+ 35
- 0
src/components/CreateReview/ThirdStep/ThirdStepCreateReview.styled.js Vedi File

@@ -0,0 +1,35 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import { ReactComponent as Logo } from "../../../assets/images/svg/logo-image.svg";
import selectedTheme from "../../../themes";

export const ThirdStepCreateReviewContainer = styled(Box)`
width: 100%;
height: 400px;
text-align: center;
`;
export const LogoImage = styled(Logo)`
margin-left: auto;
margin-right: auto;
text-align: center;
align-self: center;
align-content: center;
margin-top: 100px;
margin-bottom: 4px;
`;
export const MainTitle = styled(Typography)`
font-weight: 700;
font-size: 24px;
font-family: "Open Sans";
text-align: center;
width: 100%;
color: ${selectedTheme.primaryPurple};
`;
export const AltTitle = styled(Typography)`
width: 100%;
text-align: center;
font-family: "Open Sans";
font-weight: 400;
font-size: 16px;
color: ${selectedTheme.primaryPurple};
`;

+ 84
- 0
src/components/DirectChat/DirectChat.js Vedi File

@@ -0,0 +1,84 @@
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import { DirectChatContainer } from "./DirectChat.styled";
import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle";
import DirectChatHeader from "./DirectChatHeader/DirectChatHeader";
import { useDispatch, useSelector } from "react-redux";
// import { fetchOneOffer } from "../../store/actions/offers/offersActions";
import { useLocation, useRouteMatch } from "react-router-dom";
import { fetchOneChat } from "../../store/actions/chat/chatActions";
import {
// selectLatestChats,
selectSelectedChat,
} from "../../store/selectors/chatSelectors";
import DirectChatContent from "./DirectChatContent/DirectChatContent";
import { selectOffer } from "../../store/selectors/offersSelectors";
import { fetchOneOffer } from "../../store/actions/offers/offersActions";

const DirectChat = () => {
const chat = useSelector(selectSelectedChat);
const offer = useSelector(selectOffer);
const routeMatch = useRouteMatch();
const location = useLocation();
// const allChats = useSelector(selectLatestChats);
// const foundChat = useMemo(
// () => allChats.find((item) => item?.chat?._id === chat?.chat?._id),
// [chat, allChats]
// );
const offerObject = useMemo(() => {
if (location?.state?.offerId) {
return offer?.offer;
}
return chat?.offer?.offer;
}, [chat, location.state, offer]);
const chatObject = useMemo(() => {
if (location?.state?.offerId) {
return {};
}
return chat?.chat;
}, [chat, location.state]);
const interlocutorObject = useMemo(() => {
if (location?.state?.offerId) {
return {
image: offer?.companyData?.image,
name: offer?.companyData?.company?.name,
location: offer?.companyData?.company?.contacts?.location
}
}
return chat?.interlocutor;
}, [chat,location.state, offer]);
console.log("offerObject: ", offerObject);
console.log("chatObject: ", chatObject);
console.log("interlucatorObject: ", interlocutorObject);
const dispatch = useDispatch();
useEffect(() => {
console.log(location.state)
if (routeMatch.params.idChat && location.state?.offerId) {
refreshChat();
}
}, [routeMatch.params.idChat, location.state?.offerId]);
const refreshChat = () => {
if (routeMatch.params.idChat === "newMessage") {
dispatch(fetchOneOffer(location.state.offerId))
} else {
dispatch(fetchOneChat(routeMatch.params.idChat));
}
};
return (
<DirectChatContainer>
<DirectChatHeaderTitle />
<DirectChatHeader offer={offerObject} interlocutor={interlocutorObject} />
<DirectChatContent
chat={chatObject}
interlucator={interlocutorObject}
refreshChat={refreshChat}
/>
</DirectChatContainer>
);
};

DirectChat.propTypes = {
children: PropTypes.node,
};

export default DirectChat;

+ 5
- 0
src/components/DirectChat/DirectChat.styled.js Vedi File

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

export const DirectChatContainer = styled(Box)`
`

+ 62
- 0
src/components/DirectChat/DirectChatContent/DirectChatContent.js Vedi File

@@ -0,0 +1,62 @@
import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import {
DirectChatContentContainer,
MessageContainer,
MessagesList,
} from "./DirectChatContent.styled";
import DirectChatContentHeader from "./DirectChatContentHeader/DirectChatContentHeader";
import MessageCard from "../../Cards/MessageCard/MessageCard";
import { useSelector } from "react-redux";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import { selectMineProfilePicture } from "../../../store/selectors/profileSelectors";
import DirectChatNewMessage from "../DirectChatNewMessage/DirectChatNewMessage";

const DirectChatContent = (props) => {
const messages = props?.chat?.messages;
const userId = useSelector(selectUserId);
const myProfileImage = useSelector(selectMineProfilePicture);
const messagesRef = useRef(null);
const interlucatorProfileImage = props?.interlucator?.image;
const handleRefresh = () => {
props.refreshChat();
};
useEffect(() => {
const offsetBottom =
messagesRef.current?.offsetTop + messagesRef.current?.offsetHeight;
messagesRef.current?.scrollTo({ top: offsetBottom, behaviour: "smooth" });
}, [messages]);
return (
<DirectChatContentContainer>
<DirectChatContentHeader interlucator={props?.interlucator} />
<MessagesList ref={messagesRef}>
{messages?.map((item) => {
const isMyMessage = userId === item.userId;
const image = isMyMessage ? myProfileImage : interlucatorProfileImage;
return (
<MessageContainer key={item?._id}>
<MessageCard
message={item}
image={image}
isMyMessage={isMyMessage}
/>
</MessageContainer>
);
})}
</MessagesList>
<DirectChatNewMessage
chatId={props?.chat?._id}
refreshChat={handleRefresh}
/>
</DirectChatContentContainer>
);
};

DirectChatContent.propTypes = {
children: PropTypes.node,
chat: PropTypes.any,
interlucator: PropTypes.any,
refreshChat: PropTypes.func,
};

export default DirectChatContent;

+ 38
- 0
src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js Vedi File

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

export const DirectChatContentContainer = styled(Box)`
border: 1px solid ${selectedTheme.borderNormal};
margin-top: 18px;
border-radius: 4px;
min-height: 600px;
background-color: white;
position: relative;
`;
export const MessagesList = styled(Box)`
padding: 18px 36px;
position: relative;
max-height: 425px;
height: 425px;
overflow-y: auto;
display: flex;
flex-direction: column;
/* justify-content: flex-end; */
/* align-items: flex-end; */
@media (max-width: 600px) {
padding: 18px 0;
}
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: #ddd;
}
&::-webkit-scrollbar-thumb {
background: #777;
}
scrollbar-width: thin;
scrollbar-color: #ddd;
`;
export const MessageContainer = styled(Box)``;

+ 45
- 0
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js Vedi File

@@ -0,0 +1,45 @@
import React from "react";
import PropTypes from "prop-types";
import {
DirectChatContentHeaderContainer,
DirectChatContentHeaderFlexContainer,
PhoneIcon,
PhoneIconContainer,
ProfileDetails,
ProfileImage,
ProfileLocation,
ProfileLocationIcon,
ProfileLocationText,
ProfileName,
} from "./DirectChatContentHeader.styled";

const DirectChatContentHeader = (props) => {
return (
<DirectChatContentHeaderContainer>
<DirectChatContentHeaderFlexContainer>
<ProfileImage src={props?.interlucator?.image} />
<ProfileDetails>
<ProfileName>{props?.interlucator?.name}</ProfileName>
<ProfileLocation>
<ProfileLocationIcon />
<ProfileLocationText>
{props?.interlucator?.location}
</ProfileLocationText>
</ProfileLocation>
</ProfileDetails>
</DirectChatContentHeaderFlexContainer>
<DirectChatContentHeaderFlexContainer>
<PhoneIconContainer>
<PhoneIcon />
</PhoneIconContainer>
</DirectChatContentHeaderFlexContainer>
</DirectChatContentHeaderContainer>
);
};

DirectChatContentHeader.propTypes = {
children: PropTypes.node,
interlucator: PropTypes.any,
};

export default DirectChatContentHeader;

+ 75
- 0
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js Vedi File

@@ -0,0 +1,75 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../../themes";
import {ReactComponent as Location} from "../../../../assets/images/svg/location.svg"
import {ReactComponent as Phone} from "../../../../assets/images/svg/phone.svg"
import { IconButton } from "../../../Buttons/IconButton/IconButton";


export const DirectChatContentHeaderContainer = styled(Box)`
height: 90px;
background-color: ${selectedTheme.chatHeaderColor};
width: 100%;
display: flex;
flex-direction: row;
padding: 17px;
padding-left: 35px;
justify-content: space-between;
`
export const DirectChatContentHeaderFlexContainer = styled(Box)`
display: flex;
flex-direction: row;
`
export const ProfileImage = styled.img`
width: 54px;
height: 54px;
border-radius: 100%;
overflow: hidden;
`
export const ProfileDetails = styled(Box)`
display: flex;
flex-direction: column;
margin-left: 18px;
`
export const ProfileName = styled(Box)`
font-weight: 600;
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryPurple};
`
export const ProfileLocation = styled(Box)`
display: flex;
flex-direction: row;
`
export const ProfileLocationText = styled(Box)`
color: ${selectedTheme.primaryDarkText};
font-size: 12px;
font-family: "Open Sans";
margin-left: 5.5px;

`
export const ProfileLocationIcon = styled(Location)`
width: 12px;
height: 12px;
position: relative;
top: 2px;
`
export const PhoneIcon = styled(Phone)`
position: relative;
top: 2.5px;
left: 1.5px;
`
export const PhoneIconContainer = styled(IconButton)`
background-color: white;
width: 40px;
height: 40px;
border-radius: 100%;
transition: .2s all;
&:hover button:hover {
background-color: ${selectedTheme.primaryIconBackgroundColor};
}
&:hover {
background-color: ${selectedTheme.primaryIconBackgroundColor};
cursor: pointer;
}
`

+ 79
- 0
src/components/DirectChat/DirectChatHeader/DirectChatHeader.js Vedi File

@@ -0,0 +1,79 @@
import React, { useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";
import { DirectChatHeaderContainer } from "./DirectChatHeader.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import { useSelector } from "react-redux";
import { selectExchange } from "../../../store/selectors/exchangeSelector";
import { useDispatch } from "react-redux";
import { fetchExchange } from "../../../store/actions/exchange/exchangeActions";
import { selectSelectedChat } from "../../../store/selectors/chatSelectors";
import BackdropComponent from "../../MUI/BackdropComponent";
import CreateReview from "../../CreateReview/CreateReview";
import { selectUserId } from "../../../store/selectors/loginSelectors";

const DirectChatHeader = (props) => {
const exchange = useSelector(selectExchange);
const userId = useSelector(selectUserId);
const dispatch = useDispatch();
const chat = useSelector(selectSelectedChat);
const [showReviewModal, setShowReviewModal] = useState(false);
useEffect(() => {
if (chat?.chat?.exchangeId) {
refetchExchange();
}
}, [chat]);
const isDisabledReviews = useMemo(() => {
if (!exchange.valid) return true;
if (exchange.seller?.userId === userId) {
if (exchange.seller?.givenReview) return true;
}
if (exchange.buyer?.userId === userId) {
if (exchange.buyer?.givenReview) return true;
}
return false;
}, [exchange, userId])
const refetchExchange = () => {
dispatch(fetchExchange(chat.chat.exchangeId));
}
const makeReview = () => {
setShowReviewModal(true);
};
const handleGiveReviewSuccess = () => {
refetchExchange();
}
return (
<DirectChatHeaderContainer>
{showReviewModal && (
<>
<BackdropComponent
isLoading
handleClose={() => setShowReviewModal(false)}
position="fixed"
/>
<CreateReview
offer={props.offer}
interlocutor={props.interlocutor}
closeModal={() => setShowReviewModal()}
handleGiveReviewSuccess={handleGiveReviewSuccess}
exchange={exchange}
/>
</>
)}
<OfferCard
offer={props.offer}
aboveChat
disabledReviews={isDisabledReviews}
makeReview={makeReview}
dontShowViews
/>
</DirectChatHeaderContainer>
);
};

DirectChatHeader.propTypes = {
children: PropTypes.node,
offer: PropTypes.any,
interlocutor: PropTypes.any,
};

export default DirectChatHeader;

+ 6
- 0
src/components/DirectChat/DirectChatHeader/DirectChatHeader.styled.js Vedi File

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

export const DirectChatHeaderContainer = styled(Box)`

`

+ 20
- 0
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js Vedi File

@@ -0,0 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import { DirectChatHeaderTitleContainer, HeaderTitleContent, MessageIcon } from './DirectChatHeaderTitle.styled'
import { useTranslation } from 'react-i18next'

const DirectChatHeaderTitle = () => {
const {t} = useTranslation();
return (
<DirectChatHeaderTitleContainer>
<MessageIcon />
<HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent>
</DirectChatHeaderTitleContainer>
)
}

DirectChatHeaderTitle.propTypes = {
children: PropTypes.node,
}

export default DirectChatHeaderTitle

+ 17
- 0
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.styled.js Vedi File

@@ -0,0 +1,17 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import {ReactComponent as Message} from "../../../assets/images/svg/message.svg";

export const DirectChatHeaderTitleContainer = styled(Box)`

`
export const MessageIcon = styled(Message)`

`
export const HeaderTitleContent = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
position: relative;
left: 9px;
bottom: 7px;
`

+ 71
- 0
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js Vedi File

@@ -0,0 +1,71 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import {
DirectChatNewMessageContainer,
NewMessageField,
SendButton,
} from "./DirectChatNewMessage.styled";
import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes";
import { useDispatch } from "react-redux";
import { sendMessage, startNewChat } from "../../../store/actions/chat/chatActions";
import { useHistory, useLocation } from "react-router-dom";

const DirectChatNewMessage = (props) => {
const [typedValue, setTypedValue] = useState("");
const dispatch = useDispatch();
const { t } = useTranslation();
const location = useLocation();
const history = useHistory();
const handleApiResponseSuccess = () => {
props.refreshChat();
};
const handleSend = () => {
if (location.state?.offerId) {
initiateNewChat(typedValue);
} else {
dispatch(
sendMessage({
message: typedValue,
chatId: props.chatId,
handleApiResponseSuccess,
})
);
}
setTypedValue("");
};
const handleMessageSendSuccess = (newChatId) => {
console.log("NEW CHAT ID: ", newChatId);
history.replace(`${newChatId}`);
}
const initiateNewChat = (typedValue) => {
const offerId = location.state.offerId;
dispatch(startNewChat({offerId, message: typedValue, handleMessageSendSuccess}))
}
return (
<DirectChatNewMessageContainer>
<NewMessageField
placeholder={t("messages.sendPlaceholder")}
fullWidth
italicPlaceholder
value={typedValue}
onChange={(typed) => setTypedValue(typed.target.value)}
/>
<SendButton
onClick={handleSend}
buttoncolor={selectedTheme.primaryPurple}
variant="contained"
>
{t("messages.send")}
</SendButton>
</DirectChatNewMessageContainer>
);
};

DirectChatNewMessage.propTypes = {
children: PropTypes.node,
chatId: PropTypes.any,
refreshChat: PropTypes.func,
};

export default DirectChatNewMessage;

+ 41
- 0
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.styled.js Vedi File

@@ -0,0 +1,41 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { TextField } from "../../TextFields/TextField/TextField";

export const DirectChatNewMessageContainer = styled(Box)`
border-top: 1px solid ${selectedTheme.messageBackground};
height: 82px;
position: absolute;
bottom: 0;
width: calc(100% - 72px);
margin-left: 36px;
margin-right: 36px;
padding-top: 18px;
display: flex;
flex-direction: row;
gap: 36px;
@media (max-width: 600px) {
margin-left: 18px;
margin-right: 18px;
width: calc(100% - 36px);
gap: 18px;
}
`;
export const NewMessageField = styled(TextField)`
height: 48px;
margin: 0;
flex-grow: 1;
& div {
height: 48px;
}
`;
export const SendButton = styled(PrimaryButton)`
width: 180px;
height: 48px;
@media (max-width: 600px) {
width: 30vw;
float: right;
}
`;

+ 80
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumn.js Vedi File

@@ -0,0 +1,80 @@
import React, { useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";
import { MiniChatColumnContainer } from "./MiniChatColumn.styled";
import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard";
import { useDispatch, useSelector } from "react-redux";
import {
selectLatestChats,
selectSelectedChat,
} from "../../../store/selectors/chatSelectors";
import { fetchChats } from "../../../store/actions/chat/chatActions";
import MiniChatColumnHeader from "./MiniChatColumnHeader/MiniChatColumnHeaderTitle";
import { useLocation } from "react-router-dom";
import { selectOffer } from "../../../store/selectors/offersSelectors";

const MiniChatColumn = () => {
const chats = useSelector(selectLatestChats);
const [chatsToShow, setChatsToShow] = useState([]);
const selectedChat = useSelector(selectSelectedChat);
const [isThereNewChat, setIsThereNewChat] = useState(false);
const offer = useSelector(selectOffer);
const location = useLocation();
const dispatch = useDispatch();
const newChat = useMemo(() => {
if (location.state?.offerId) {
return {
interlocutorData: {
image: offer?.companyData?.image,
name: offer?.companyData?.company?.name
},
offerData: {
name: offer?.offer?.name
}
}
}
return {}
}, [offer, location.state])
useEffect(() => {
if (location.state?.offerId) {
setIsThereNewChat(true);
} else {
if (isThereNewChat !== false) {
dispatch(fetchChats());
setIsThereNewChat(false);
}
}
}, [location.state])

useEffect(() => {
setChatsToShow([...chats]);
}, [chats])

useEffect(() => {
dispatch(fetchChats());
}, []);
return (
<MiniChatColumnContainer>
<MiniChatColumnHeader />
{isThereNewChat && (
<MiniChatCard
chat={newChat}
selected
/>
)}
{chatsToShow.map((item) => {
return (
<MiniChatCard
key={Date.now() * Math.random()}
chat={item}
selected={item?.chat?._id === selectedChat?.chat?._id && !isThereNewChat}
/>
)})}
</MiniChatColumnContainer>
);
};

MiniChatColumn.propTypes = {
children: PropTypes.node,
};

export default MiniChatColumn;

+ 8
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js Vedi File

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

export const MiniChatColumnContainer = styled(Box)`
@media (max-width: 600px) {
display: none;
}
`

+ 20
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js Vedi File

@@ -0,0 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import { HeaderTitleContent, MailIcon, MiniChatColumnHeaderContainer } from './MiniChatColumnHeaderTitle.styled'
import { useTranslation } from 'react-i18next'

const MiniChatColumnHeader = () => {
const {t} = useTranslation();
return (
<MiniChatColumnHeaderContainer>
<MailIcon/>
<HeaderTitleContent>{t("messages.miniChatHeaderTitle")}</HeaderTitleContent>
</MiniChatColumnHeaderContainer>
)
}

MiniChatColumnHeader.propTypes = {
children: PropTypes.node,
}

export default MiniChatColumnHeader

+ 25
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.styled.js Vedi File

@@ -0,0 +1,25 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import {ReactComponent as Mail} from "../../../../assets/images/svg/mail.svg"
import selectedTheme from "../../../../themes";


export const MiniChatColumnHeaderContainer = styled(Box)`
margin-bottom: 10px;
`
export const MailIcon = styled(Mail)`
width: 20px;
height: 20px;
position: relative;
top: -1px;
& path {
stroke: ${selectedTheme.primaryDarkTextThird};
}
`
export const HeaderTitleContent = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
position: relative;
left: 9px;
bottom: 7px;
`

+ 143
- 0
src/components/Header/Drawer/Drawer.js Vedi File

@@ -0,0 +1,143 @@
import React from "react";
import PropTypes from "prop-types";
import {
AddOfferButton,
AuthButtonsDrawerContainer,
CloseButton,
CloseIcon,
DrawerButton,
DrawerContainer,
DrawerOption,
FooterButtons,
HeaderTitle,
LoginButton,
LogoutButton,
LogoutIcon,
LogoutText,
MailIcon,
MyUsername,
PackageIcon,
RegisterButton,
ToolsContainer,
UserIcon,
} from "./Drawer.styled";
import { useSelector } from "react-redux";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { CHAT_PAGE, LOGIN_PAGE, MY_OFFERS_PAGE, REGISTER_PAGE } from "../../../constants/pages";
import { selectProfileName } from "../../../store/selectors/profileSelectors";

export const Drawer = (props) => {
const user = useSelector(selectUserId);
const { t } = useTranslation();
const history = useHistory();
const name = useSelector(selectProfileName);

const goToMyPosts = () => {
props.toggleDrawer();
history.push(MY_OFFERS_PAGE);
};
const goToMyMessages = () => {
props.toggleDrawer();
history.push(CHAT_PAGE);
};
const goToMyProfile = () => {
props.toggleDrawer();
history.push(`/profile/${user}`)
};
const goToRegister = () => {
props.toggleDrawer();
history.push(REGISTER_PAGE);
};
const goToLogin = () => {
props.toggleDrawer();
history.push(LOGIN_PAGE);
};
const handleAddOffer = () => {
props.toggleDrawer();
props.addOffer();
}
const logoutUser = () => {};
return (
<DrawerContainer>
<CloseButton onClick={props.toggleDrawer}>
<CloseIcon />
</CloseButton>
{user ? (
<React.Fragment>
<HeaderTitle>{t("header.navMenu")}</HeaderTitle>
<ToolsContainer mobile>
<DrawerButton onClick={goToMyPosts}>
<IconButton sx={{ borderRadius: "4px" }}>
<PackageIcon />
</IconButton>
<DrawerOption>{t("header.myOffers")}</DrawerOption>
</DrawerButton>
<DrawerButton onClick={goToMyMessages} >
<IconButton sx={{ borderRadius: "4px" }}>
<MailIcon />
</IconButton>
<DrawerOption>{t("header.myMessages")}</DrawerOption>
</DrawerButton>
<DrawerButton onClick={goToMyProfile} >
<IconButton sx={{ borderRadius: "4px" }}>
<UserIcon />
</IconButton>
<DrawerOption>{t("header.myProfile")}</DrawerOption>
<MyUsername>({name})</MyUsername>
</DrawerButton>
</ToolsContainer>
<FooterButtons>
<AddOfferButton
type="submit"
variant="contained"
buttoncolor={selectedTheme.primaryYellow}
textcolor="black"
onClick={handleAddOffer}
>
{t("header.addOffer")}
</AddOfferButton>
<LogoutButton>
<IconButton onClick={logoutUser}>
<LogoutIcon />
</IconButton>
<LogoutText>{t("common.logout")}</LogoutText>
</LogoutButton>
</FooterButtons>
</React.Fragment>
) : (
<AuthButtonsDrawerContainer>
<RegisterButton
type="submit"
variant="contained"
height="36px"
buttoncolor={selectedTheme.primaryYellow}
textcolor={selectedTheme.primaryDarkText}
onClick={goToRegister}
>
{t("register.headerTitle")}
</RegisterButton>
<LoginButton
type="submit"
variant="contained"
height="36px"
buttoncolor={selectedTheme.primaryPurple}
textcolor={selectedTheme.primaryIconBackgroundColor}
onClick={goToLogin}
>
{t("login.headerTitle")}
</LoginButton>
</AuthButtonsDrawerContainer>
)}
</DrawerContainer>
);
};

Drawer.propTypes = {
children: PropTypes.node,
toggleDrawer: PropTypes.func,
addOffer: PropTypes.func,
};

+ 156
- 0
src/components/Header/Drawer/Drawer.styled.js Vedi File

@@ -0,0 +1,156 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import IconButton from "../../IconButton/IconButton";
import {ReactComponent as Close} from "../../../assets/images/svg/close-modal.svg"
import selectedTheme from "../../../themes";
import {ReactComponent as User} from "../../../assets/images/svg/user.svg"
import {ReactComponent as Mail} from "../../../assets/images/svg/mail.svg"
import {ReactComponent as Package} from "../../../assets/images/svg/package.svg"
import {ReactComponent as Logout} from "../../../assets/images/svg/log-out.svg"


export const DrawerContainer = styled(Box)`
width: 100vw;
position: relative;
height: 100%;
`
export const ToolsContainer = styled(Box)`
display: flex;
flex-direction: column;
justify-content: ${(props) => (props.mobile ? "center" : "space-between")};
align-items: ${(props) => (props.mobile ? "start" : "center")};
${(props) => !props.mobile && `width: 100%;`}
& div button {
${(props) => props.mobile && `width: auto;`}
}
position: absolute;
top: 0px;
bottom: 70px;
left: 36px;
gap: 36px;
`;

export const AuthButtonsDrawerContainer = styled(Box)`
height: 100%;
width: 100%;
`;
export const LoginButton = styled(PrimaryButton)`
height: 49px;
width: 218px;
font-weight: 600;
position: absolute;
bottom: 85px;
right: 0;
left: 0;
margin-left: auto;
margin-right: auto;
`;
export const RegisterButton = styled(PrimaryButton)`
height: 49px;
width: 218px;
font-weight: 600;
position: absolute;
bottom: 40px;
right: 0;
left: 0;
margin-left: auto;
margin-right: auto;
`;
export const CloseButton = styled(IconButton)`
position: absolute;
top: 40px;
right: 36px;
`
export const CloseIcon = styled(Close)`
color: ${selectedTheme.primaryYellow};
width: 16px;
height: 16px;
`
export const DrawerOption = styled(Typography)`
font-weight: 600;
font-family: "Open Sans";
color: ${selectedTheme.primaryPurple};
font-size: 18px;
position: relative;
top: 4px;
`
export const DrawerButton = styled(Box)`
display: flex;
flex-direction: row;
`
export const UserIcon = styled(User)`
width: 24px;
height: 24px;
margin-right: 9px;
& path {
stroke: ${selectedTheme.primaryYellow};
}
`
export const MailIcon = styled(Mail)`
width: 24px;
height: 24px;
margin-right: 9px;
& path {
stroke: ${selectedTheme.primaryYellow};
}
`
export const PackageIcon = styled(Package)`
width: 24px;
height: 24px;
margin-right: 9px;
& path {
stroke: ${selectedTheme.primaryYellow};
}
`
export const AddOfferButton = styled(PrimaryButton)`
width: 165px;
height: 44px;
position: relative;
bottom: -5px;
`
export const FooterButtons = styled(Box)`
position: absolute;
bottom: 36px;
display: flex;
flex-direction: row;
width: 100vw;
justify-content: space-around;
`
export const LogoutButton = styled(Box)`
`
export const LogoutIcon = styled(Logout)`
width: 20px;
height: 20px;
& path {
stroke: ${selectedTheme.primaryPurple};
}
`
export const LogoutText = styled(Typography)`
font-weight: 600;
font-size: 14px;
color: ${selectedTheme.primaryPurple};
font-family: "Open Sans";
position: relative;
left: -14px;
top: -3px;
`
export const HeaderTitle = styled(Typography)`
font-weight: 700;
font-family: "Open Sans";
font-size: 18px;
color: ${selectedTheme.primaryDarkTextThird};
position: absolute;
top: 36px;
left: 36px;
`
export const MyUsername = styled(Typography)`
font-size: 12px;
font-family: "Open Sans";
color: ${selectedTheme.primaryPurple};
position: relative;
top: 12px;
left: 4px;
letter-spacing: 2%;
`


+ 38
- 114
src/components/Header/Header.js Vedi File

@@ -1,9 +1,7 @@
import React, { useState, useMemo, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
AddOfferButton,
AuthButtonsContainer,
AuthButtonsDrawerContainer,
DrawerContainer,
EndIcon,
FilterContainer,
FilterIcon,
@@ -21,20 +19,13 @@ import {
UserName,
} from "./Header.styled";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
Toolbar,
useMediaQuery,
Typography,
} from "@mui/material";
import { AppBar, Badge, Toolbar, useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/system";
import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
import MailIcon from "@mui/icons-material/EmailOutlined";
import Autorenew from "@mui/icons-material/Autorenew";
import AccountCircle from "@mui/icons-material/PersonOutlineOutlined";
import Drawer from "../MUI/DrawerComponent";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import PopoverComponent from "../Popovers/PopoverComponent";
import { MyPosts } from "../Popovers/MyPosts/MyPosts";
import { MyMessages } from "../Popovers/MyMessages/MyMessages";
@@ -48,16 +39,24 @@ 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 { HOME_PAGE, LOGIN_PAGE, REGISTER_PAGE } from "../../constants/pages";
import {
FORGOT_PASSWORD_MAIL_SENT,
FORGOT_PASSWORD_PAGE,
HOME_PAGE,
LOGIN_PAGE,
REGISTER_PAGE,
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 { fetchMineProfile } from "../../store/actions/profile/profileActions";
import CreateOffer from "../Cards/CreateOfferCard/CreateOffer";
import { Drawer as HeaderDrawer } from "./Drawer/Drawer";

const Header = () => {
const [openDrawer, setOpenDrawer] = useState(false);
const Header = (props) => {
const [openFilters, setOpenFilters] = useState(false);
const [showSearchBar, setShowSearchBar] = useState(true);
const [numberOfFilters, setNumberOfFilters] = useState(0);
@@ -75,8 +74,10 @@ const Header = () => {
const filters = useFilters();
const searchMobileRef = useRef(null);
const queryStringHook = useQueryString();
const [openDrawer, setOpenDrawer] = useState(false);

useEffect(() => {
dispatch(fetchMineProfile());
dispatch(fetchMineProfile());
}, []);
useEffect(() => {
setUserPopoverOpen(false);
@@ -92,7 +93,7 @@ const Header = () => {
} else {
setShowSearchBar(true);
}
}, [history.location.pathname])
}, [history.location.pathname]);
useEffect(() => {
setNumberOfFilters(filters.calculateFiltersChosen());
}, [
@@ -136,12 +137,14 @@ const Header = () => {

useEffect(() => {
let shouldShowHeader = true;
console.log(props);
if (
location.pathname === "/login" ||
location.pathname === "/register" ||
location.pathname === "/register/success" ||
location.pathname === "/forgot-password" ||
location.pathname === "/reset-password" ||
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 === "/"
) {
shouldShowHeader = false;
@@ -156,11 +159,7 @@ const Header = () => {
setUserPopoverOpen(false);
setMsgPopoverOpen(false);
setPostsPopoverOpen(false);
}, [location.pathname])

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

let listener;
const handleFocusSearch = () => {
@@ -274,21 +190,24 @@ const Header = () => {
setOpenFilters((prevState) => !prevState);
};

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

const handleLogoClick = () => {
history.push({
pathname: HOME_PAGE,
state: {
logo: true
}
logo: true,
},
});
}
};

return (
<HeaderContainer style={{ display: shouldShow ? "block" : "none" }}>
<AppBar
elevation={0}
position="fixed"
// positionFixed
sx={{ backgroundColor: "white", zIndex: "80" }}
>
<Toolbar>
@@ -300,7 +219,12 @@ const Header = () => {
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={drawerContent}
content={
<HeaderDrawer
toggleDrawer={handleToggleDrawer}
addOffer={() => setShowCreateOfferModal(true)}
/>
}
/>
)}
<SearchInput

+ 11
- 10
src/components/Header/Header.styled.js Vedi File

@@ -47,16 +47,7 @@ export const DrawerContainer = styled(Box)`
flex-direction: column;
}
`;
export const ToolsContainer = styled(Box)`
display: flex;
flex-direction: row;
justify-content: ${(props) => (props.mobile ? "center" : "space-between")};
align-items: ${(props) => (props.mobile ? "start" : "center")};
${(props) => !props.mobile && `width: 100%;`}
& div button {
${(props) => props.mobile && `width: auto;`}
}
`;

export const LogoContainer = styled(Box)`
display: flex;
justify-content: center;
@@ -122,6 +113,16 @@ export const UserName = styled(Typography)`
font-weight: 600;
white-space: nowrap;
`;
export const ToolsContainer = styled(Box)`
display: flex;
flex-direction: row;
justify-content: ${(props) => (props.mobile ? "center" : "space-between")};
align-items: ${(props) => (props.mobile ? "start" : "center")};
${(props) => !props.mobile && `width: 100%;`}
& div button {
${(props) => props.mobile && `width: auto;`}
}
`;
export const RegisterButton = styled(PrimaryButton)`
height: 49px;
width: 180px;

+ 6
- 0
src/components/ImagePicker/ImagePicker.styled.js Vedi File

@@ -16,6 +16,7 @@ export const ImagePickerContainer = styled(Box)`
border-radius: 4px;
position: relative;
cursor: pointer;
overflow: hidden;
background-color: ${selectedTheme.imagePickerBackground};
background-image: linear-gradient(
to right,
@@ -68,6 +69,11 @@ export const ImageUploaded = styled.img`
object-fit: cover;
z-index: 1;
`;
export const ImageUploadedContainer = styled(Box)`
width: 144px;
height: 144px;
overflow: hidden;
`
export const ImageOverlay = styled(Box)`
position: absolute;
top: 0;

+ 9
- 10
src/components/ItemDetails/Header/Header.js Vedi File

@@ -1,9 +1,9 @@
import React from 'react';
import React from "react";
import PropTypes from "prop-types";
import { useHistory } from "react-router-dom";
//import { IconButton } from "../../Buttons/IconButton/IconButton";
import { HeaderContainer, HeaderText, ButtonContainer } from './Header.styled';
import { ArrowButton } from '../../Buttons/ArrowButton/ArrowButton';
import { HeaderContainer, HeaderText, ButtonContainer } from "./Header.styled";
import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton";

// const DownArrow = (props) => (
// <IconStyled {...props}>
@@ -11,21 +11,19 @@ import { ArrowButton } from '../../Buttons/ArrowButton/ArrowButton';
// </IconStyled>
// );

const Header = () => {

const Header = (props) => {
const history = useHistory();

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

return (
<HeaderContainer onClick={handleBackButton}>
<HeaderContainer onClick={handleBackButton} component="header" className={props.className}>
<ButtonContainer>
<ArrowButton side={"left"}></ArrowButton>
<ArrowButton side={"left"}></ArrowButton>
<HeaderText>Nazad na objave</HeaderText>
</ButtonContainer>
</ButtonContainer>
</HeaderContainer>
);
};
@@ -36,6 +34,7 @@ Header.propTypes = {
isGrid: PropTypes.bool,
filters: PropTypes.array,
category: PropTypes.string,
className: PropTypes.string,
};
Header.defaultProps = {
isGrid: false,

+ 19
- 23
src/components/ItemDetails/ItemDetails.js Vedi File

@@ -1,32 +1,28 @@
import React, { useMemo } from 'react';
import React, { useMemo } from "react";
import Header from "./Header/Header";
import { useSelector } from "react-redux";
import { ItemDetailsContainer } from "./ItemDetails.styled";
import ItemDetailsCard from "../Cards/ItemDetailsCard/ItemDetailsCard";
import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard";
import { selectOffer } from '../../store/selectors/offersSelectors';
import { selectUserId } from '../../store/selectors/loginSelectors';
// import { useHistory } from 'react-router-dom';


import { selectOffer } from "../../store/selectors/offersSelectors";
import { selectUserId } from "../../store/selectors/loginSelectors";

const ItemDetails = () => {
const offer = useSelector(selectOffer);
const userId = useSelector(selectUserId);
let isMyProfile = useMemo(() => {
if (offer?.offer?.userId?.toString() === userId.toString()) {
return true;
}
return false;
}, [offer, userId])
console.log(isMyProfile)
return (
<ItemDetailsContainer>
<Header/>
<ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} />
<ItemDetailsCard offer={offer} isMyOffer={isMyProfile}/>
</ItemDetailsContainer>
)
}
const offer = useSelector(selectOffer);
const userId = useSelector(selectUserId);
let isMyProfile = useMemo(() => {
if (offer?.offer?.userId?.toString() === userId.toString()) {
return true;
}
return false;
}, [offer, userId]);
return (
<ItemDetailsContainer>
<Header />
<ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} />
<ItemDetailsCard offer={offer} isMyOffer={isMyProfile} />
</ItemDetailsContainer>
);
};

export default ItemDetails;

+ 20
- 12
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js Vedi File

@@ -22,10 +22,15 @@ import { ReactComponent as PIB } from "../../../assets/images/svg/pib.svg";
import { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg";
import selectedTheme from "../../../themes";
import { useHistory } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";

const ItemDetailsHeaderCard = (props) => {
const history = useHistory();
const chats = useSelector(selectLatestChats);
const offer = props.offer;
const userId = useSelector(selectUserId);
if (!props.offer) {
return <div>Loading...</div>;
}
@@ -42,6 +47,20 @@ const ItemDetailsHeaderCard = (props) => {
const handleGoProfile = () => {
history.push(`/profile/${offer?.offer?.userId}`);
};
const messageUser = (offer) => {
const chatItem = chats.find(
(item) => item.chat.offerId === offer?.offer?._id
);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`);
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id,
});
}
}
};
return (
<ItemDetailsHeaderContainer
isMyProfile={props.isMyProfile}
@@ -79,7 +98,7 @@ const ItemDetailsHeaderCard = (props) => {
<UserIcon />
</UserIconContainer>
) : (
<MessageIcon>
<MessageIcon onClick={() => messageUser(offer)}>
<MessageColor />
</MessageIcon>
)}
@@ -121,17 +140,6 @@ ItemDetailsHeaderCard.propTypes = {
sponsored: PropTypes.bool,
offer: PropTypes.any,
isMyProfile: PropTypes.bool,
// offer: PropTypes.shape({
// images: PropTypes.any,
// name:PropTypes.string,
// description:PropTypes.string,
// category:PropTypes.shape({
// name:PropTypes.string
// }),
// location:PropTypes.shape({
// city:PropTypes.string
// })
// })
};
ItemDetailsHeaderCard.defaultProps = {
halfwidth: false,

+ 16
- 9
src/components/MarketPlace/Header/Header.js Vedi File

@@ -9,6 +9,8 @@ import {
HeaderOptions,
HeaderSelect,
IconStyled,
MySwapsTitle,
RefreshIcon,
SelectOption,
} from "./Header.styled";
import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg";
@@ -71,7 +73,7 @@ const Header = (props) => {
for (const sortOption in sortEnum) {
if (sortEnum[sortOption].value === event.target.value) {
chosenOption = sortEnum[sortOption];
sorting.changeSorting(chosenOption)
sorting.changeSorting(chosenOption);
}
}
};
@@ -79,15 +81,19 @@ const Header = (props) => {
return (
<HeaderContainer>
<Tooltip title={headerString}>
{headerString === "Sve kategorije" &&
(sorting.selectedSortOption === sortEnum.INITIAL ||
sorting.selectedSortOption === sortEnum.NEW) ? (
<React.Fragment>
<HeaderLocation initial>{headerString}</HeaderLocation>
<HeaderAltLocation>{t("header.newOffers")}</HeaderAltLocation>
</React.Fragment>
{props.myOffers !== true ? (
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>
<HeaderOptions>
@@ -140,6 +146,7 @@ Header.propTypes = {
isGrid: PropTypes.bool,
filters: PropTypes.any,
category: PropTypes.string,
myOffers: PropTypes.bool,
};
Header.defaultProps = {
isGrid: false,

+ 18
- 0
src/components/MarketPlace/Header/Header.styled.js Vedi File

@@ -4,6 +4,7 @@ import selectedTheme from "../../../themes";
import { IconButton } from "../../Buttons/IconButton/IconButton";
import Option from "../../Select/Option/Option";
import Select from "../../Select/Select";
import {ReactComponent as Refresh} from "../../../assets/images/svg/refresh.svg"

export const HeaderContainer = styled(Box)`
margin-top: 20px;
@@ -93,3 +94,20 @@ export const HeaderAltLocation = styled(Typography)`
display: none;
}
`
export const RefreshIcon = styled(Refresh)`
width: 18px;
height: 18px;
position: relative;
top: 3px;
left: -5px;
& path {
stroke: ${selectedTheme.primaryDarkTextThird};
}
`
export const MySwapsTitle = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
color: ${selectedTheme.primaryDarkTextThird};
position: relative;
left: 9px;
`

+ 4
- 3
src/components/MarketPlace/MarketPlace.js Vedi File

@@ -4,19 +4,20 @@ import { MarketPlaceContainer } from "./MarketPlace.styled";
import Header from "./Header/Header";
import Offers from "./Offers/Offers";

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

return (
<MarketPlaceContainer>
<Header isGrid={isGrid} setIsGrid={setIsGrid} />
<Offers isGrid={isGrid} />
<Header isGrid={isGrid} setIsGrid={setIsGrid} myOffers={props.myOffers} />
<Offers isGrid={isGrid} myOffers={props.myOffers} />
</MarketPlaceContainer>
);
};

MarketPlace.propTypes = {
children: PropTypes.node,
myOffers: PropTypes.bool,
};

export default MarketPlace;

+ 177
- 23
src/components/MarketPlace/Offers/Offers.js Vedi File

@@ -1,10 +1,14 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard";
import { fetchOffers } from "../../../store/actions/offers/offersActions";
import {
fetchMineOffers,
fetchOffers,
} from "../../../store/actions/offers/offersActions";
import { useDispatch, useSelector } from "react-redux";
import {
selectMineOffers,
selectOffers,
selectPinnedOffers,
selectTotalOffers,
@@ -14,16 +18,39 @@ import { HOME_PAGE } from "../../../constants/pages";
import { useHistory } from "react-router-dom";
import { useQueryString } from "../../../hooks/useQueryString";
import OffersNotFound from "./OffersNotFound";
import {
selectSelectedCategory,
selectSelectedLocations,
selectSelectedSortOption,
selectSelectedSubcategory,
} from "../../../store/selectors/filtersSelectors";
import { sortEnum } from "../../../enums/sortEnum";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { fetchChats } from "../../../store/actions/chat/chatActions";
import { selectUserId } from "../../../store/selectors/loginSelectors";

const Offers = (props) => {
const [page, setPage] = useState(1);
// const [pinnedLength, setPinnedLength] = useState(0);
const pinnedOffers = useSelector(selectPinnedOffers);
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
const selectedSortOption = useSelector(selectSelectedSortOption);
const offers = useSelector(selectOffers);
const mineOffers = useSelector(selectMineOffers);
const chats = useSelector(selectLatestChats);
const total = useSelector(selectTotalOffers);
const history = useHistory();
const dispatch = useDispatch();
const offersRef = useRef(null);
const queryStringHook = useQueryString();
const queryStringHook = useQueryString(props.myOffers);
const userId = useSelector(selectUserId);

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

useEffect(() => {
let queryObject = queryStringHook.getQueryObject();
if (queryObject.page && queryObject.page !== 1) {
@@ -32,7 +59,7 @@ const Offers = (props) => {
}, [history.location.search]);

useEffect(() => {
if (history?.location?.state?.logo) {
if (history?.location?.state?.logo || history?.location?.state?.refetch) {
dispatch(fetchOffers({ queryString: "" }));
setPage(1);
history.location.state = undefined;
@@ -41,22 +68,7 @@ const Offers = (props) => {

useEffect(() => {
if (queryStringHook.loadedFromURL) {
dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString }));
history.push({
pathname: HOME_PAGE,
search: queryStringHook.getGlobalQueryString(),
});
window.scrollTo({
top: 0,
behavior: "smooth",
});
const queryObject = new URLSearchParams(queryStringHook.queryString);
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString())
setPage(parseInt(queryObject.get("page")));
} else {
setPage(1);
}
refetch();
} else {
queryStringHook.appendMultipleToQueryString([
{ key: "size", value: "10" },
@@ -70,16 +82,152 @@ const Offers = (props) => {
if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString()) {
queryStringHook.appendToQueryString("page", page);
} else {
refetch();
}
} else {
queryStringHook.appendToQueryString("page", page);
}
}, [page]);

const pinnedOffersToShow = useMemo(() => {
if (props.myOffers) {
return mineOffers.filter((item) => item.pinned === true);
}
return pinnedOffers;
}, [pinnedOffers, mineOffers, page, props.myOffers]);

const offersToShow = useMemo(() => {
if (props.myOffers) {
return mineOffers.filter((item) => item.pinned === false);
}
return offers;
}, [offers, mineOffers, page, props.myOffers]);

const allOffersToShow = useMemo(() => {
let newOffers = [...pinnedOffersToShow, ...offersToShow];
if (props.myOffers) {
if (selectedCategory && selectedCategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.category.name === selectedCategory.name
);
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.subcategory === selectedSubcategory.name
);
}
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;
});
}
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.slice((page - 1) * 10, page * 10);
}
return newOffers;
}, [pinnedOffersToShow, offersToShow, props.myOffers, page]);

const totalOffers = useMemo(() => {
if (props.myOffers) {
return mineOffers?.length;
}
return total;
}, [mineOffers, total]);

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

const refetch = () => {
if (!props.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("page")) {
if (queryObject.get("page") !== page.toString())
setPage(parseInt(queryObject.get("page")));
} else {
setPage(1);
}
};

const messageOneUser = (offer) => {
const chatItem = chats.find((item) => item.chat.offerId === offer?._id);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`);
} else {
if (offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?._id,
});
}
}
};

return (
<>
{offers.length === 0 ? (
@@ -87,13 +235,14 @@ const Offers = (props) => {
) : (
<OffersContainer ref={offersRef}>
<>
{pinnedOffers != undefined &&
pinnedOffers.map((item) => {
{allOffersToShow != undefined &&
allOffersToShow.map((item) => {
return (
<OfferCard
key={item._id}
offer={item}
halfwidth={props.isGrid}
messageUser={messageOneUser}
/>
);
})}
@@ -109,7 +258,7 @@ const Offers = (props) => {
);
})}
<Paging
totalElements={total}
totalElements={totalOffers}
elementsPerPage={10}
current={page}
changePage={handleDifferentPage}
@@ -123,6 +272,11 @@ const Offers = (props) => {
Offers.propTypes = {
children: PropTypes.node,
isGrid: PropTypes.bool,
myOffers: PropTypes.bool,
};

Offers.defaultProps = {
myOffers: false,
};

export default Offers;

+ 5
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.styled.js Vedi File

@@ -52,6 +52,11 @@ export const PopoverListItemTextContainer = styled(ListItemText)`
cursor: pointer;
}
& p {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
max-height: 32px;
font-size: 0.81rem;
& svg {
position: relative;

+ 18
- 11
src/components/Popovers/MyMessages/MyMessages.js Vedi File

@@ -8,15 +8,6 @@ import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { selectUserId } from "../../../store/selectors/loginSelectors";
import HeaderPopover from "../HeaderPopover/HeaderPopover";

const convertMessages = (messages) => {
return messages.map((item) => ({
alt: "Tekst",
src: item.interlocutorData.image,
title: item.interlocutorData.name,
text: item?.chat?.messages[0]?.text,
}));
};

export const MyMessages = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
@@ -25,8 +16,24 @@ export const MyMessages = () => {
const history = useHistory();
const [lastChats, setLastChats] = useState([]);

const goToMessage = (chatId) => {
history.push(`/messages/${chatId}`);
};

const convertMessages = (messages) => {
return messages
.map((item) => ({
alt: "Tekst",
src: item.interlocutorData.image,
title: item.interlocutorData.name,
onClick: () => goToMessage(item?.chat?._id),
text: item?.chat?.messages[item?.chat?.messages?.length - 1]?.text,
}))
.slice(0, 2);
};

useEffect(() => {
if (userId?.length > 1) {
if (userId?.length > 1 && chats?.length === 0) {
dispatch(fetchHeaderChats(userId));
}
}, [userId]);
@@ -37,7 +44,7 @@ export const MyMessages = () => {
}, [chats]);
const goToMessages = () => {
history.push(CHAT_PAGE);
}
};
return (
<HeaderPopover
title={t("header.myMessages")}

+ 5
- 0
src/components/Popovers/MyPosts/MyPosts.js Vedi File

@@ -22,6 +22,7 @@ import { selectMineOffers } from "../../../store/selectors/offersSelectors";
import { fetchMineOffers } from "../../../store/actions/offers/offersActions";
import { selectProfileName } from "../../../store/selectors/profileSelectors";
import { useHistory } from "react-router-dom";
import { MY_OFFERS_PAGE } from "../../../constants/pages";

export const MyPosts = () => {
const { t } = useTranslation();
@@ -72,11 +73,15 @@ export const MyPosts = () => {
const goToOffer = (id) => {
history.push(`/proizvodi/${id}`)
}
const goToMySwaps = () => {
history.push(MY_OFFERS_PAGE);
}
return (
<HeaderPopover
title={t("header.myOffers")}
items={arrayOfMineOffers}
buttonText={t("header.checkEverything")}
buttonOnClick={goToMySwaps}
/>
);
};

+ 1
- 1
src/components/Popovers/MyProfile/MyProfile.js Vedi File

@@ -29,7 +29,7 @@ export const MyProfile = () => {
setProfileAsArray([
{
alt: "Profile",
src: `${profile.image}`,
src: profile.image,
title: profile.company.name,
onClick: () => seeMyProfile(),
text: (

+ 21
- 2
src/components/Profile/ProfileOffers/ProfileOffers.js Vedi File

@@ -23,14 +23,33 @@ import { useTranslation } from "react-i18next";
import { useRef } from "react";
import { selectProfileOffers } from "../../../store/selectors/offersSelectors";
import useScreenDimensions from "../../../hooks/useScreenDimensions";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { useHistory } from "react-router-dom";
import { selectUserId } from "../../../store/selectors/loginSelectors";

const ProfileOffers = (props) => {
const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [offersToShow, setOffersToShow] = useState([]);
const searchRef = useRef(null);
const chats = useSelector(selectLatestChats);
const profileOffers = useSelector(selectProfileOffers);
const dimensions = useScreenDimensions();
const history = useHistory();
const { t } = useTranslation();
const userId = useSelector(selectUserId);

const messageUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?.offer?._id);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`)
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id
})
}
}
}
useEffect(() => {
let newOffersToShow = [...offersToShow];
@@ -136,12 +155,12 @@ const ProfileOffers = (props) => {
<OffersContainer>
{dimensions.width > 600 ? (
offersToShow.map((item) => (
<OfferCard isMyOffer={props.isMyProfile} offer={item} key={JSON.stringify(item)} pinned />
<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 />))}
<OfferCard vertical isMyOffer={props.isMyProfile} offer={item} key={JSON.stringify(item)} pinned messageUser={messageUser} />))}
</OffersScroller>
)}
</OffersContainer>

+ 13
- 10
src/components/ProfileCard/ProfileCard.js Vedi File

@@ -22,9 +22,7 @@ import {
MessageIcon,
MessageButton,
} 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";
@@ -35,6 +33,7 @@ import { selectUserId } from "../../store/selectors/loginSelectors";
import { useState } from "react";
import { fetchProfileOffers } from "../../store/actions/offers/offersActions";
import EditProfile from "./EditProfile";
import { useTranslation } from "react-i18next";

const ProfileCard = () => {
const [isMyProfile, setIsMyProfile] = useState(false);
@@ -44,7 +43,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,7 +78,6 @@ const ProfileCard = () => {
document.body.style.overflow = "auto";
}

console.log(profile);
return (
<>
<ProfileCardContainer>
@@ -90,7 +89,7 @@ const ProfileCard = () => {
sx={{ mb: 1.4 }}
>
<PersonOutlineIcon color="action" sx={{ mr: 0.9 }} />
<HeaderTitle>Moj Profil</HeaderTitle>
<HeaderTitle>{t("profile.myProfile")}</HeaderTitle>
</Grid>
<ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}>
{isMyProfile ? (
@@ -139,7 +138,7 @@ const ProfileCard = () => {
>
<PocketIcon />
<ProfilePIB isMyProfile={isMyProfile} variant="subtitle2">
PIB: {profile?.company?.PIB}
{t("profile.PIB")} {profile?.company?.PIB}
</ProfilePIB>
</ProfilePIBContainer>
</Grid>
@@ -185,11 +184,13 @@ const ProfileCard = () => {
sx={{ width: "fit-content" }}
>
<StatsItem variant="subtitle2">
<b>{profile?.statistics?.publishes?.count}</b> objava
<b>{profile?.statistics?.publishes?.count}</b>
{t("profile.publishes")}
</StatsItem>

<StatsItem variant="subtitle2">
<b>{percentOfSucceededExchanges}%</b> uspešna komunikacija
<b>{percentOfSucceededExchanges}%</b>
{t("profile.successComunication")}
</StatsItem>
</Grid>
<Grid
@@ -200,10 +201,12 @@ const ProfileCard = () => {
sx={{ width: "fit-content" }}
>
<StatsItem variant="subtitle2">
<b>{profile?.statistics?.views?.count}</b> ukupnih pregleda
<b>{profile?.statistics?.views?.count}</b>
{t("profile.numberOfViews")}
</StatsItem>
<StatsItem variant="subtitle2">
<b>{percentOfSucceededExchanges}%</b> korektna saradnja
<b>{percentOfSucceededExchanges}%</b>
{t("profile.successCooperation")}
</StatsItem>
</Grid>
</ProfileStats>

+ 1
- 1
src/components/Router/PrivateRoute.js Vedi File

@@ -12,7 +12,7 @@ const PrivateRoute = ({ ...props }) => {
const isUserAuthenticated = useMemo(() => {
if (userId?.length === 0) return false;
return true;
})
}, [userId])

useEffect(() => {
if (!isUserAuthenticated) {

+ 27
- 0
src/components/UserReviews/NoReviews/NoReviews.js Vedi File

@@ -0,0 +1,27 @@
import React from "react";
import PropTypes from "prop-types";
import useScreenDimensions from "../../../hooks/useScreenDimensions";
import {
NoReviewsAltText,
NoReviewsContainer,
NoReviewsText,
} from "./NoReviews.styled";
import UserReviewsSkeleton from "./UserReviewsSkeleton/UserReviewsSkeleton";
import { useTranslation } from "react-i18next";
const NoReviews = () => {
const { width } = useScreenDimensions();
const { t } = useTranslation();
return (
<NoReviewsContainer>
<NoReviewsText>{t("reviews.title")}</NoReviewsText>
<NoReviewsAltText>{t("reviews.altTitle")}</NoReviewsAltText>
<UserReviewsSkeleton numOfElements={width < 600 ? 1 : 2} />
</NoReviewsContainer>
);
};

NoReviews.propTypes = {
children: PropTypes.node,
};

export default NoReviews;

+ 23
- 0
src/components/UserReviews/NoReviews/NoReviews.styled.js Vedi File

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

export const NoReviewsContainer = styled(Box)`
`
export const NoReviewsText = styled(Typography)`
color: ${selectedTheme.primaryPurple};
font-size: 24px;
font-family: "Open Sans";
text-align: center;
font-weight: 700;
width: 100%;
`
export const NoReviewsAltText = styled(Typography)`
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
font-family: "Open Sans";
text-align: center;
width: 100%;
margin-bottom: 36px;
`

src/components/UserReviewsCard/UserReviewsSkeleton/UserReviewsSkeleton.js → src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.js Vedi File


src/components/UserReviewsCard/UserReviewsSkeleton/UserReviewsSkeleton.styled.js → src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.styled.js Vedi File

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

export const UserReviewsSkeletonContainer = styled(Box)`
width: 100%;

+ 94
- 0
src/components/UserReviews/UserReviews.js Vedi File

@@ -0,0 +1,94 @@
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import {
ReviewList,
ReviewsBox,
ReviewsHeader,
ReviewsTitle,
} from "./UserReviews.styled";

import StarBorderIcon from "@mui/icons-material/StarBorder";
import { useTranslation } from "react-i18next";
import UserReviewsCard from "../Cards/UserReviewsCard/UserReviewsCard";
import NoReviews from "./NoReviews/NoReviews";
import { useDispatch, useSelector } from "react-redux";
import { selectOffer } from "../../store/selectors/offersSelectors";
import { selectSelectedReviews } from "../../store/selectors/reviewSelector";
import { useRouteMatch } from "react-router-dom";
import { fetchReviews } from "../../store/actions/review/reviewActions";

const UserReviews = (props) => {
const { t } = useTranslation();
const offer = useSelector(selectOffer);
const reviews = useSelector(selectSelectedReviews);
const routeMatch = useRouteMatch();
const dispatch = useDispatch();

useEffect(() => {
console.log(routeMatch)
if (props.profileReviews && routeMatch.params?.idProfile) {
let idProfile = routeMatch.params.idProfile;
dispatch(fetchReviews(idProfile));
}
}, [props.profileReviews, routeMatch])
const lastThreeReviews = useMemo(() => {
if (props.givingReview) {
return [...props.profileReviews];
}
if (props.isProfileReviews) {
return [...reviews.slice(0, 3)];
}
if (offer?.companyData?.lastThreeReviews) {
return [...offer?.companyData.lastThreeReviews];
}
return [];
}, [props.profileReviews, offer, props.isProfileReviews, reviews]);

return (
<ReviewsBox
className={props.className}
numOfReviews={lastThreeReviews?.length}
>
{!props.givingReview && (
<ReviewsHeader
container
direction="row"
justifyContent="start"
alignItems="center"
sx={{ mb: 1.4 }}
>
<StarBorderIcon color="action" sx={{ mr: 0.9 }} />
<ReviewsTitle>{t("reviews.rates")}</ReviewsTitle>
</ReviewsHeader>
)}
<ReviewList>
{lastThreeReviews?.length > 0 ? (
lastThreeReviews?.map((review, index) => (
<UserReviewsCard
review={review}
key={index}
givingReview={props.givingReview}
/>
))
) : (
<NoReviews></NoReviews>
)}
</ReviewList>
</ReviewsBox>
);
};

UserReviews.propTypes = {
children: PropTypes.node,
heading: PropTypes.string,
isProfileReviews: PropTypes.bool,
profileReviews: PropTypes.any,
className: PropTypes.string,
givingReview: PropTypes.bool,
};
UserReviews.defaultProps = {
isProfileReviews: false,
profileReviews: [],
};

export default UserReviews;

+ 131
- 0
src/components/UserReviews/UserReviews.styled.js Vedi File

@@ -0,0 +1,131 @@
import styled from "styled-components";
import { List, Box, Typography, Grid } from "@mui/material";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import ThumbDownIcon from "@mui/icons-material/ThumbDown";
import selectedTheme from "../../themes";

export const ReviewsBox = styled(Box)`
width: 100%;
/* One review is 185px in height and 82 px are header title + padding */
/* height: ${props => props.numOfReviews > 0 ? props.numOfReviews * 185 + 82 + 'px' : `calc(100% - 90px)`}; */
/* max-height: 100vh; */
@media (max-width: 1200px) {
padding: 0;
}
@media (max-width: 600px) {
position: relative;
top: -45px;
overflow: hidden;
max-height: ${props => props.numOfReviews > 2 ? "650px" : props.numOfReviews > 1 ? "450px" : "350px"};
padding: 0;
}
`;
export const ReviewsHeader = styled(Grid)`
@media (max-width: 800px) {
display: none;
}
`

export const ReviewsTitle = styled(Typography)`
font-family: "Open Sans";
font-size: 16px;
@media (max-width: 800px) {
display: none;
}
`

export const ReviewList = styled(List)`
background: white;
padding: 2rem;
border-radius: 4px 0 0 4px;
padding-right: .4rem;
height: 100%;
width: 100%;
border: 1px solid ${selectedTheme.borderNormal};

/* overflow-y: auto; */
&::-webkit-scrollbar {
width: 5px;
}
&::-webkit-scrollbar-track {
background: #ddd;
}
&::-webkit-scrollbar-thumb {
background: #777;
}
scrollbar-width: thin;
scrollbar-color: #ddd;
`;

export const ThumbUp = styled(ThumbUpIcon)`
position: relative;
left: -8px;
`
export const ThumbDown = styled(ThumbDownIcon)`
position: relative;
left: -8px;
`
export const NoReviewsContainer = styled(Box)`
`
export const NoReviewsText = styled(Typography)`
color: ${selectedTheme.primaryPurple};
font-size: 24px;
font-family: "Open Sans";
text-align: center;
font-weight: 700;
width: 100%;
`
export const NoReviewsAltText = styled(Typography)`
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
font-family: "Open Sans";
text-align: center;
width: 100%;
margin-bottom: 36px;
`
export const ProfileImage = styled.img`
width: 54px;
height: 54px;
border-radius: 100%;
`
export const ProfileImageContainer = styled(Box)`
width: 54px;
height: 54px;
border-radius: 100%;
margin-right: 14px;
`
export const ReviewQuote = styled(Grid)`
position: relative;
left: 8px;
`
export const ThumbBox = styled(Grid)`
`
export const ReviewQuoteBox = styled(Grid)`
`
export const ReviewQuoteText = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
`
export const ReviewDetails = styled(Grid)`
`
export const ReviewDetailsText = styled(Typography)`
font-family: "Open Sans";
font-size: 12px;
color: ${selectedTheme.primaryDarkText};
font-style: italic;
letter-spacing: 0.02em;
`
export const ReviewDetailsValue = styled(Typography)`
color: ${selectedTheme.primaryPurple};
font-style: normal;
font-weight: 600;
`
export const ProfileName = styled(Typography)`
font-weight: 600;
font-size: 16px;
font-family: "Open Sans";
`
export const ReviewContainer = styled(Box)`
`

+ 0
- 143
src/components/UserReviewsCard/UserReviewsCard.js Vedi File

@@ -1,143 +0,0 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import { NoReviewsAltText, NoReviewsContainer, NoReviewsText, ReviewList, ReviewsBox, ReviewsHeader, ReviewsTitle, ThumbDown, ThumbUp } from "./UserReviewsCard.styled";

import {
Avatar,
Grid,
ListItem,
ListItemAvatar,
Typography,
Divider,
} from "@mui/material";
// import Mockupdata from "./Mockupdata";
import StarBorderIcon from "@mui/icons-material/StarBorder";
import selectedTheme from "../../themes";
import { useSelector } from "react-redux";
import { selectOffer } from "../../store/selectors/offersSelectors";
import { selectProfile } from "../../store/selectors/profileSelectors";
// import { selectUserId } from "../../store/selectors/loginSelectors";
import { useTranslation } from "react-i18next";
import UserReviewsSkeleton from "./UserReviewsSkeleton/UserReviewsSkeleton";
import useScreenDimensions from "../../hooks/useScreenDimensions";

const UserReviewsCard = (props) => {
const {t} = useTranslation();
// const userId = useSelector(selectUserId);
const offer = useSelector(selectOffer);
const dimensions = useScreenDimensions();
const profile = useSelector(selectProfile);
console.log("offer Reviews: ", offer);
console.log("profile reviews: ", profile);
console.log("props.profileReviews: ", props.profileReviews);
// const profileId = useMemo(() => {
// if (props.profileReviews) {
// return profile._id;
// }
// return offer?.offer?.userId;
// }, [props.profileReviews, offer, profile])
// const isMyProfile = useMemo(() => {
// if (userId === profileId) return true;
// return false;
// }, [profileId, userId]);
const lastThreeReviews = useMemo(() => {
if (props.profileReviews) {
return [];
}
return offer?.companyData?.lastThreeReviews;
}, [props.profileReviews, offer, profile])

const NoReviews = () => {
return (
<NoReviewsContainer>
<NoReviewsText>{t("reviews.title")}</NoReviewsText>
<NoReviewsAltText>{t("reviews.altTitle")}</NoReviewsAltText>
<UserReviewsSkeleton numOfElements={dimensions.width < 600 ? 1 : 2} />
</NoReviewsContainer>
)
}

return (
<>
<ReviewsBox>
<ReviewsHeader
container
direction="row"
justifyContent="start"
alignItems="center"
sx={{ mb: 1.4 }}
>
<StarBorderIcon color="action" sx={{ mr: 0.9 }} />
<ReviewsTitle>Ocene</ReviewsTitle>
</ReviewsHeader>
<ReviewList>
{lastThreeReviews?.length > 0 ? lastThreeReviews?.map((review) => (
<>
<ListItem
alignItems="flex-start"
sx={{ alignItems: "center", mt: 2 }}
>
<ListItemAvatar sx={{ mt: 0 }}>
<Avatar alt={review.name} src="/static/images/avatar/1.jpg" />
</ListItemAvatar>
<Typography sx={{ color: selectedTheme.primaryPurple }}>
<b>{review.name}</b>
</Typography>
</ListItem>
<Grid
container
direction="row"
justifyContent="start"
alignItems="center"
spacing={2}
sx={{ pl: 2, py: 2 }}
>
<Grid item xs={1}>
{review.isGood ? (
<ThumbUp color="success" />
) : (
<ThumbDown color="error" />
)}
</Grid>
<Grid item xs={11}>
<Typography
sx={{ display: "inline" }}
component="span"
variant="body2"
color="text.primary"
>
&quot;{review?.quote}&quot;
</Typography>
</Grid>
</Grid>
<Grid sx={{ pl: 2, pb: 2 }}>
<Typography variant="body2" sx={{ display: "block" }}>
Korektna komunikacija: <b>{review.isGoodCommunication}</b>
</Typography>
<Typography variant="body2" sx={{ display: "block" }}>
Uspešna trampa: <b>{review.isSuccessfulSwap}</b>
</Typography>
</Grid>
{review.id < review?.length - 1 ? (
<Divider variant="inset" component="li" sx={{ ml: 0 }} />
) : (
<></>
)}
</>
)) : (<NoReviews></NoReviews>)}
</ReviewList>
</ReviewsBox>
</>
);
};

UserReviewsCard.propTypes = {
children: PropTypes.node,
heading: PropTypes.string,
profileReviews: PropTypes.bool,
};
UserReviewsCard.defaultProps = {
profileReviews: false,
}

export default UserReviewsCard;

+ 2
- 1
src/constants/pages.js Vedi File

@@ -12,4 +12,5 @@ export const CREATE_OFFER_PAGE = "/create-offer";
export const ITEM_DETAILS_PAGE = "/proizvodi/:idProizvod";
export const PROFILE_PAGE = "/profile/:idProfile"
export const CHAT_PAGE = "/messages";
export const CHAT_MESSAGE_PAGE = "/messages/:idUser";
export const CHAT_MESSAGE_PAGE = "/messages/:idChat";
export const MY_OFFERS_PAGE = "/myoffers"

+ 14
- 0
src/enums/reviewEnum.js Vedi File

@@ -0,0 +1,14 @@
export const reviewEnum = {
YES: {
value: 1,
mainText: "Da",
},
NO: {
value: 2,
mainText: "Ne"
},
NOT_BAD: {
value: 3,
mainText: "Može bolje"
}
}

+ 13
- 1
src/hooks/useFilters.js Vedi File

@@ -24,7 +24,7 @@ import { useQueryString } from "./useQueryString";



const useFilters = () => {
const useFilters = (myOffers) => {
const selectedCategory = useSelector(selectSelectedCategory);
const selectedSubcategory = useSelector(selectSelectedSubcategory);
const selectedLocations = useSelector(selectSelectedLocations);
@@ -56,6 +56,10 @@ const useFilters = () => {
(item) => item.name === queryObject.get("category").toString()
);
setSelectedCategory(category);
} else {
if (!myOffers) {
setSelectedCategory();
}
}
if (queryObject.has("subcategory")) {
setSelectedSubcategory(
@@ -64,6 +68,10 @@ const useFilters = () => {
item.name.toString() === queryObject.get("subcategory").toString()
)
);
} else {
if (!myOffers) {
setSelectedSubcategory();
}
}
if (queryObject.has("location")) {
let locationsToPush = [];
@@ -79,6 +87,10 @@ const useFilters = () => {
// );
// }
setSelectedLocations([...locationsToPush]);
} else {
if (!myOffers) {
setSelectedLocations([]);
}
}
}
}, [queryStringHook.queryString, categories, locations]);

+ 6
- 7
src/hooks/useQueryString.js Vedi File

@@ -1,13 +1,12 @@
/* eslint-disable */
import _ from "lodash";
import { useEffect, useState } from "react";
// import _ from "lodash"
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 useFilters from "./useFilters";
// import useSorting from "./useSorting";
// import { sortEnum } from "../enums/sortEnum";
import { convertQueryStringBackend, convertQueryStringFrontend } from "../util/helpers/queryHelpers";

export const useQueryString = () => {
@@ -44,9 +43,9 @@ export const useQueryString = () => {
}, [queryString, loadedFromURL]);

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

+ 14
- 11
src/hooks/useSorting.js Vedi File

@@ -25,15 +25,18 @@ const useSorting = () => {
let queryObject = new URLSearchParams(
convertQueryStringFrontend(queryString)
);
if (queryObject.has("sortBy"))
if (queryObject.has("sortBy")) {
if (queryObject.get("sortBy") === "newest") {
setSelectedSortOption(sortEnum.NEW);
}
if (queryObject.get("sortBy") === "oldest") {
setSelectedSortOption(sortEnum.OLD);
}
if (queryObject.get("sortBy") === "popular") {
setSelectedSortOption(sortEnum.POPULAR);
if (queryObject.get("sortBy") === "oldest") {
setSelectedSortOption(sortEnum.OLD);
}
if (queryObject.get("sortBy") === "popular") {
setSelectedSortOption(sortEnum.POPULAR);
}
} else {
setSelectedSortOption(sortOptions.INITIAL);
}
}
}, [queryStringHook.queryString, queryStringHook.loadedFromURL]);
@@ -58,23 +61,23 @@ const useSorting = () => {
}
if (_des_popular !== null) {
queryArray.push({ key: "_des_popular", value: `${_des_popular}` });
queryArray.push({ key: "_des_date" })
queryArray.push({ key: "_des_date" });
}
if (shouldGoFirstPage) {
queryArray.push({key: "page", value: "1"});
queryArray.push({ key: "page", value: "1" });
}
queryStringHook.appendMultipleToQueryString(queryArray);
};

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

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

+ 26
- 0
src/i18n/resources/rs.js Vedi File

@@ -22,6 +22,7 @@ export default {
labelPhone: "Telefon",
labelLocation: "Lokacija",
labelWebsite: "Adresa Websajta",
logout: "Odjavi se",
next: "Sledeće",
nextPage: "Sledeća strana",
previousPage: "Prethodna strana",
@@ -157,6 +158,7 @@ export default {
supportedImagesFormats:
"Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>",
continue: "NASTAVI",
publish: "OBJAVI",
},
apiErrors: {
somethingWentWrong: "Greska sa serverom!",
@@ -170,10 +172,26 @@ export default {
checkEverything: "POGLEDAJ SVE",
myMessages: "Moje poruke",
newOffers: "Najnovije ponude",
navMenu: "Navigacioni Meni",
},
reviews: {
title: "Ova kompanija još uvek nema ocenu.",
altTitle: "Budite prvi da je ocenite",
modalTitle: "Ocenjivanje kompanije",
isCorrectCommunication: "Korektna komunikacija",
hasExchangeSucceed: "Uspešna trampa",
comment: "Komentar",
commentError: "Komentar mora imati minimum 5 karaktera!",
selectFieldError: "Odaberite jedno polje!",
leaveComment: "Ostavi komentar",
rates: "Ocene",
},
messages: {
headerTitle: "Moje Ćaskanje",
cardProduct: "Proizvod: ",
miniChatHeaderTitle: "Moje Poruke",
send: "Pošalji",
sendPlaceholder: "Poruka...",
},
editProfile: {
website: "Web Sajt*",
@@ -204,4 +222,12 @@ 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",
successComunication: "uspešna komunikacija",
numberOfViews: "ukupnih pregleda",
successCooperation: "korektna saradnja",
},
};

+ 26
- 21
src/layouts/ChatGridLayout/ChatGridLayout.js Vedi File

@@ -1,27 +1,32 @@
import { ChatGridLayoutContainer } from "./ChatGridLayout.styled"

import React from "react";
import PropTypes from "prop-types";
import {
ChatGridLayoutContainer,
GridContainer,
GridContent,
GridLeftCard,
} from "./ChatGridLayout.styled";

export const ChatGridLayout = (props) => {
return (
<ChatGridLayoutContainer>
{props.children}
<Grid container maxHeight="xl" spacing={2}>
<Content item xs={12} lg={9} xl={9.6} md={8} >
return (
<ChatGridLayoutContainer>
{props.children}
<GridContainer container maxHeight="xl" spacing={2}>
<GridLeftCard item xs={0} lg={3} xl={2.4} md={4}>
{props.leftCard}
</GridLeftCard>
<GridContent item xs={12} lg={9} xl={9.6} md={8}>
{props.content}
</Content>
<RightCard item xs={0} lg={3} xl={2.4} md={4} >
{props.rightCard}
</RightCard>
</Grid>
</ChatGridLayoutContainer>
)
}
</GridContent>
</GridContainer>
</ChatGridLayoutContainer>
);
};

ChatGridLayout.propTypes = {
children: PropTypes.node,
content: PropTypes.node,
rightCard: PropTypes.node,
};
children: PropTypes.node,
content: PropTypes.node,
leftCard: PropTypes.node,
};

export default ChatGridLayout
export default ChatGridLayout;

+ 16
- 1
src/layouts/ChatGridLayout/ChatGridLayout.styled.js Vedi File

@@ -1,6 +1,21 @@
import { Grid } from "@mui/material";
import { Container } from "@mui/system";
import styled from "styled-components";


export const ChatGridLayoutContainer = styled(Container)`
`;
margin-top: 120px;
margin-left: 0;
margin-right: 0;
max-width: 100%;
@media (max-width: 600px) {
margin-top: 60px;
}
`;
export const GridContainer = styled(Grid)`

`
export const GridContent = styled(Grid)`
`
export const GridLeftCard = styled(Grid)`
`

+ 23
- 18
src/layouts/ChatLayout/ChatLayout.js Vedi File

@@ -1,24 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ChatContent, ChatLayoutContainer } from './ChatLayout.styled';

import React from "react";
import PropTypes from "prop-types";
import { ChatContent, ChatLayoutContainer } from "./ChatLayout.styled";

export const ChatLayout = (props) => {
return (
<ChatLayoutContainer>
{props.children}
return (
<ChatLayoutContainer>
{props.children}

<ChatContent maxHeight="xl" spacing={2} justifyContent={"center"} item xs={12} md={10}>
{props.content}
</ChatContent>
</ChatLayoutContainer>
)
}
<ChatContent
maxHeight="xl"
spacing={2}
justifyContent={"center"}
item
xs={12}
md={10}
>
{props.content}
</ChatContent>
</ChatLayoutContainer>
);
};

ChatLayout.propTypes = {
children: PropTypes.node,
content: PropTypes.node,
};

children: PropTypes.node,
content: PropTypes.node,
};

export default ChatLayout;
export default ChatLayout;

+ 2
- 2
src/layouts/ItemDetailsLayout/ItemDetailsLayout.styled.js Vedi File

@@ -11,8 +11,8 @@ export const ItemDetailsLayoutContainer = styled(Container)`
position: relative;
flex: 1;
height: 100%;
@media (max-width: 1024px) {
padding-right: 18px;
@media (max-width: 1200px) {
padding-right: 60px;
}
@media (max-width: 600px) {
padding-left: 18px;

+ 4
- 3
src/pages/ChatMessages/ChatMessages.js Vedi File

@@ -1,11 +1,12 @@
import React from "react";
import { ChatMessagesPageContainer } from "./ChatMessages.styled"
import DirectChat from "../../components/DirectChat/DirectChat";
import MiniChatColumn from "../../components/DirectChat/MiniChatColumn/MiniChatColumn";
import ChatGridLayout from "../../layouts/ChatGridLayout/ChatGridLayout";


export const ChatMessagesPage = () => {
return (
<ChatMessagesPageContainer>
</ChatMessagesPageContainer>
<ChatGridLayout content={<DirectChat /> } leftCard={<MiniChatColumn />} />
)
}


+ 0
- 1
src/pages/ChatMessages/ChatMessages.styled.js Vedi File

@@ -11,5 +11,4 @@ export const ChatMessagesPageContainer = styled(Container)`
max-width: none;
flex: 1;
display: flex;
flex-direction: column;
`;

+ 4
- 2
src/pages/ItemDetailsPage/ItemDetailsPageMUI.js Vedi File

@@ -5,8 +5,8 @@ import { ItemDetailsPageContainer } from "./ItemDetailsPage.styled";
import { useDispatch} from "react-redux";
import ItemDetails from "../../components/ItemDetails/ItemDetails";
import ItemDetailsLayout from "../../layouts/ItemDetailsLayout/ItemDetailsLayout";
import UserReviewsCard from "../../components/UserReviewsCard/UserReviewsCard";
import { fetchOneOffer } from "../../store/actions/offers/offersActions";
import UserReviews from "../../components/UserReviews/UserReviews";

const ItemDetailsPage = (props) => {
const dispatch = useDispatch();
@@ -19,13 +19,15 @@ const ItemDetailsPage = (props) => {
}
}, [offerId]);

//Dodati dispatch za fetch reviews i staviti kao prop u <UserReviewsCard /> kada bude gotova metoda na BE

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

{/* <Box sx={{ mt: 4, mx: 4 }}>

+ 23
- 0
src/pages/MyOffers/MyOffers.js Vedi File

@@ -0,0 +1,23 @@
import React 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";

const MyOffers = () => {
return (
<MyOffersContainer>
<MainLayout
leftCard={<FilterCard myOffers />}
content={<MarketPlace myOffers={true} />}
/>
</MyOffersContainer>
);
};

MyOffers.propTypes = {
children: PropTypes.node,
};

export default MyOffers;

+ 6
- 0
src/pages/MyOffers/MyOffers.styled.js Vedi File

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

export const MyOffersContainer = styled(Box)`

`

+ 0
- 0
src/pages/ProfilePage/ProfilePage.js Vedi File


Dato che sono stati cambiati molti file in questo diff, alcuni di essi non verranno mostrati

Loading…
Annulla
Salva