Quellcode durchsuchen

Merged with chat-edit-delete

bugfix/520
jovan.cirkovic vor 3 Jahren
Ursprung
Commit
245774af8c
100 geänderte Dateien mit 3515 neuen und 774 gelöschten Zeilen
  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 Datei anzeigen

/* eslint-disable */
import React from "react"; import React from "react";
import { Redirect, Route, Switch } from "react-router-dom"; import { Redirect, Route, Switch } from "react-router-dom";


PROFILE_PAGE, PROFILE_PAGE,
CHAT_MESSAGE_PAGE, CHAT_MESSAGE_PAGE,
CHAT_PAGE, CHAT_PAGE,
MY_OFFERS_PAGE,
} from "./constants/pages"; } from "./constants/pages";
import LoginPage from "./pages/LoginPage/LoginPage"; import LoginPage from "./pages/LoginPage/LoginPage";
import HomePage from "./pages/HomePage/HomePageMUI"; import HomePage from "./pages/HomePage/HomePageMUI";
import ProfilePage from "./pages/ProfilePage/ProfilePage"; import ProfilePage from "./pages/ProfilePage/ProfilePage";
import ChatMessagesPage from "./pages/ChatMessages/ChatMessages"; import ChatMessagesPage from "./pages/ChatMessages/ChatMessages";
import ChatPage from "./pages/Chat/Chat"; import ChatPage from "./pages/Chat/Chat";
import MyOffers from "./pages/MyOffers/MyOffers";


const AppRoutes = () => { const AppRoutes = () => {
return ( return (
<Route path={CREATE_OFFER_PAGE} component={CreateOffer} /> <Route path={CREATE_OFFER_PAGE} component={CreateOffer} />
<Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} /> <Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} />
<Route path={PROFILE_PAGE} component={ProfilePage} /> <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_MESSAGE_PAGE} component={ChatMessagesPage} />
<PrivateRoute path={CHAT_PAGE} component={ChatPage} /> <PrivateRoute path={CHAT_PAGE} component={ChatPage} />
<PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} />


<Redirect from="*" to={NOT_FOUND_PAGE} /> <Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch> </Switch>

+ 6
- 0
src/assets/images/svg/logo-image.svg Datei anzeigen

<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 Datei anzeigen

<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 Datei anzeigen

<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 Datei anzeigen

<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 Datei anzeigen



export const IconButton = (props) => { export const IconButton = (props) => {
return <IconButtonContainer style={props.containerStyle} className={props.className}> 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} {props.children}
</IconButtonStyled> </IconButtonStyled>
</IconButtonContainer> </IconButtonContainer>
containerStyle: PropTypes.any, containerStyle: PropTypes.any,
style: PropTypes.any, style: PropTypes.any,
className: PropTypes.string, className: PropTypes.string,
iconColor: PropTypes.string
iconColor: PropTypes.string,
disabled: PropTypes.bool,
} }

+ 5
- 1
src/components/Buttons/PrimaryButton/PrimaryButton.js Datei anzeigen

style={props.containerStyle} style={props.containerStyle}
className={props.className} className={props.className}
> >
<PrimaryButtonStyled {...props} buttoncolor={props.buttoncolor} sx={props.style}>
<PrimaryButtonStyled
{...props}
buttoncolor={props.buttoncolor}
sx={props.style}
>
{props.children} {props.children}
</PrimaryButtonStyled> </PrimaryButtonStyled>
</PrimaryButtonContainer> </PrimaryButtonContainer>

+ 67
- 70
src/components/Cards/ChatCard/ChatCard.js Datei anzeigen

import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import {
CheckButton, CheckButton,
MessageIcon,
// OfferImage,
OfferImage,
OfferTitle, OfferTitle,
OfferCard, OfferCard,
ChatOffer, ChatOffer,
OfferText, OfferText,
ChatCardContainer, ChatCardContainer,
Col, Col,
// UserImage,
UserImage,
OfferCardContainer, OfferCardContainer,
UserImgWrapper, UserImgWrapper,
OfferImgWrapper, OfferImgWrapper,
LocationIcon, LocationIcon,
OfferCardContainerMobile, OfferCardContainerMobile,
OfferTextMobile, OfferTextMobile,
OfferTitleMobile
OfferTitleMobile,
PhoneIconContainer,
PhoneIcon,
} from "./ChatCard.styled"; } from "./ChatCard.styled";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg"; import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";
import selectedTheme from "../../../themes"; import selectedTheme from "../../../themes";
import {ReactComponent as DummyImage1} from "../../../assets/images/svg/dummyImages/offer-1.svg";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import useScreenDimensions from "../../../hooks/useScreenDimensions"; import useScreenDimensions from "../../../hooks/useScreenDimensions";
//import { useSelector } from "react-redux"; //import { useSelector } from "react-redux";
const ChatCard = (props) => { const ChatCard = (props) => {
const history = useHistory(); const history = useHistory();
const dimensions = useScreenDimensions(); 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) => { const routeToItem = (userId) => {
history.push(`/messages/${userId}`); history.push(`/messages/${userId}`);
};
};
return ( 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> <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> <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> </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 = { ChatCard.propTypes = {
children: PropTypes.node, children: PropTypes.node,
_id: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
category: PropTypes.string, category: PropTypes.string,
offer: PropTypes.any, offer: PropTypes.any,
pinned: PropTypes.bool, pinned: PropTypes.bool,
vertical: PropTypes.bool, vertical: PropTypes.bool,
chat: PropTypes.any,
}; };
OfferCard.defaultProps = { OfferCard.defaultProps = {
halfwidth: false, halfwidth: false,

+ 55
- 19
src/components/Cards/ChatCard/ChatCard.styled.js Datei anzeigen

import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Icon } from "../../Icon/Icon"; import { Icon } from "../../Icon/Icon";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; 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)` export const ChatCardContainer = styled(Container)`
display: flex; display: flex;
position: relative; position: relative;
justify-content: space-between; justify-content: space-between;
@media (max-width: 550px) { @media (max-width: 550px) {
height: auto;
max-height: 108px;
margin: 0;
${(props) => ${(props) =>
props.vertical && props.vertical &&
` `
width: 144px; width: 144px;
height: 144px; height: 144px;
@media (max-width: 600px) { @media (max-width: 600px) {
width: 90px;
height: 90px;
width: 72px;
height: 72px;
} }
`; `;


width: 144px; width: 144px;
height: 144px; height: 144px;
@media (max-width: 600px) { @media (max-width: 600px) {
width: 90px;
height: 90px;
width: 72px;
height: 72px;
min-width: 80px;
} }
`; `;
export const OfferImgWrapper = styled(Box)` export const OfferImgWrapper = styled(Box)`
border-radius: 4px; border-radius: 4px;
width: 72px; width: 72px;
height: 72px; height: 72px;

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



export const OfferFlexContainer = styled(Container)` export const OfferFlexContainer = styled(Container)`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
max-width: 2000px; max-width: 2000px;
position: relative; position: relative;
@media (max-width: 550px) { @media (max-width: 550px) {
} }
`; `;


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


@media (max-width: 550px) { @media (max-width: 550px) {
position: relative; position: relative;
flex: 1; flex: 1;
color: ${selectedTheme.primaryPurple}; color: ${selectedTheme.primaryPurple};
font-weight: 700; font-weight: 700;
font-size: 18px;
font-size: 12px;
cursor: pointer; cursor: pointer;
@media (max-width: 550px) { @media (max-width: 550px) {
display: block; display: block;
export const CheckButton = styled(PrimaryButton)` export const CheckButton = styled(PrimaryButton)`
width: 180px; width: 180px;
height: 48px; height: 48px;
background-color: ${selectedTheme.primaryPurple};
&:hover button {
background-color: ${selectedTheme.primaryPurple} !important;
&:hover {
background-color: ${selectedTheme.primaryPurple};
color: white !important; 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) { @media (max-width: 600px) {
display: none; display: none;
} }
transition: 0.2s all;
`; `;
export const MessageIcon = styled(IconButton)`
export const PhoneIconContainer = styled(IconButton)`
width: 40px; width: 40px;
height: 40px; height: 40px;
background-color: ${selectedTheme.primaryIconBackgroundColor}; background-color: ${selectedTheme.primaryIconBackgroundColor};
padding-top: 2px; padding-top: 2px;
text-align: center; text-align: center;
@media (max-width: 600px) { @media (max-width: 600px) {
width: 30px;
height: 30px;
width: 32px;
height: 32px;
top: 16px; top: 16px;
right: 16px; right: 16px;
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding-left: 36px;
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
} }


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


align-items: center; align-items: center;
flex-direction: row; flex-direction: row;
gap: 18px; 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)` export const UserName = styled(Typography)`


export const LastMessage = styled(Typography)` export const LastMessage = styled(Typography)`
font-family: "Open Sans"; font-family: "Open Sans";
color: ${selectedTheme.primaryText};
line-height: 16px;
color: ${selectedTheme.primaryDarkTextThird};
line-height: 22px;
font-size: 16px; 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; position: relative;
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
export const OfferImage = styled.img` export const OfferImage = styled.img`
max-width: 72px; max-width: 72px;
max-height: 72px; max-height: 72px;
min-width: 72px;
width: 72px; width: 72px;
height: 72px; height: 72px;
border-radius: 4px; border-radius: 4px;
display: none; 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 Datei anzeigen

import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux"; 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 * as Yup from "yup";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { fetchLogin } from "../../../store/actions/login/loginActions"; import { fetchLogin } from "../../../store/actions/login/loginActions";
historyRouter.location.pathname.length historyRouter.location.pathname.length
); );
dispatch(fetchProfileOffers(userId)); dispatch(fetchProfileOffers(userId));
history.push({
historyRouter.push({
pathname: HOME_PAGE, pathname: HOME_PAGE,
state: { state: {
from: history.location.pathname, from: history.location.pathname,
}; };


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


const submitEditOffer = (id, values) => { const submitEditOffer = (id, values) => {

+ 27
- 9
src/components/Cards/CreateOfferCard/CreateOffer.styled.js Datei anzeigen

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


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

}

@media screen and (max-width: 628px) { @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; padding: 0 30px;
top: 70px;
} }

`; `;


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


export const CreateOfferFormContainer = styled(Box)` export const CreateOfferFormContainer = styled(Box)`
width: 335px; width: 335px;
height: 700px; height: 700px;
padding-top: 20px;
${props => props.currentStep === 3 && `width: 120%; height: 420px;`}
`; `;
export const RegisterAltText = styled(Typography)` export const RegisterAltText = styled(Typography)`
font-family: "Poppins"; font-family: "Poppins";
cursor: auto; cursor: auto;
letter-spacing: 0.2px; letter-spacing: 0.2px;
} }
@media (max-width: 600px) {
& label {
font-size: 9px;
}
}
`; `;
export const SelectText = styled(Typography)` export const SelectText = styled(Typography)`
font-size: 16px; font-size: 16px;
position: relative; position: relative;
top: 15px; top: 15px;
margin-bottom: 18px; margin-bottom: 18px;
@media (max-width: 600px) {
height: 40px;
font-size: 12px;
}
& div { & div {
${SelectText} { ${SelectText} {
font-weight: 600; font-weight: 600;

+ 68
- 55
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js Datei anzeigen

DescriptionField, DescriptionField,
FieldLabel, FieldLabel,
NextButton, NextButton,
SelectOption,
TitleField, TitleField,
} from "./FirstPartCreateOffer.styled"; } from "./FirstPartCreateOffer.styled";
import * as Yup from "yup"; import * as Yup from "yup";
import selectedTheme from "../../../../themes"; import selectedTheme from "../../../../themes";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Option from "../../../Select/Option/Option";
import { SelectField } from "../CreateOffer.styled"; import { SelectField } from "../CreateOffer.styled";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import useScreenDimensions from "../../../../hooks/useScreenDimensions"; import useScreenDimensions from "../../../../hooks/useScreenDimensions";
}; };


return ( 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 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" margin="normal"
italicPlaceholder
value={formik.values.description}
value={formik.values.nameOfProduct}
onChange={formik.handleChange} 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 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")} /> <FieldLabel leftText={t("offer.location")} />
<SelectField <SelectField
formik.setFieldValue("location", value.target.value); formik.setFieldValue("location", value.target.value);
}} }}
> >
<Option value="default">{t("offer.choseLocation")}</Option>
<SelectOption value="default">{t("offer.choseLocation")}</SelectOption>
{locations.map((loc) => { {locations.map((loc) => {
return ( return (
<Option key={loc._if} value={loc.city}>
<SelectOption key={loc._if} value={loc.city}>
{loc.city} {loc.city}
</Option>
</SelectOption>
); );
})} })}
</SelectField> </SelectField>
formik.setFieldValue("category", value.target.value); formik.setFieldValue("category", value.target.value);
}} }}
> >
<Option value="default">{t("offer.choseCategory")}</Option>
<SelectOption value="default">{t("offer.choseCategory")}</SelectOption>
{categories.map((cat, i) => { {categories.map((cat, i) => {
return ( return (
<Option
<SelectOption
key={i} key={i}
value={cat.name} value={cat.name}
onClick={() => handleSubcategories(cat.name)} onClick={() => handleSubcategories(cat.name)}
> >
{cat.name} {cat.name}
</Option>
</SelectOption>
); );
})} })}
</SelectField> </SelectField>
formik.setFieldValue("subcategory", value.target.value); formik.setFieldValue("subcategory", value.target.value);
}} }}
> >
<Option value="default">{t("offer.choseSubcategory")}</Option>
<SelectOption value="default">{t("offer.choseSubcategory")}</SelectOption>
{subcat && {subcat &&
subcat.map((sub, i) => { subcat.map((sub, i) => {
return ( return (
<Option key={i} value={sub}>
<SelectOption key={i} value={sub}>
{sub} {sub}
</Option>
</SelectOption>
); );
})} })}
</SelectField> </SelectField>
</CreateOfferFormContainer>


<NextButton <NextButton
type="submit" type="submit"
fullWidth fullWidth
buttoncolor={selectedTheme.primaryPurple} buttoncolor={selectedTheme.primaryPurple}
textcolor="white" 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")} {t("offer.continue")}
</NextButton> </NextButton>
</CreateOfferFormContainer>
</>
); );
}; };



+ 43
- 3
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js Datei anzeigen

import selectedTheme from "../../../../themes"; import selectedTheme from "../../../../themes";
import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../../../CheckBox/Label"; import { Label } from "../../../CheckBox/Label";
import Option from "../../../Select/Option/Option";
import { TextField } from "../../../TextFields/TextField/TextField"; import { TextField } from "../../../TextFields/TextField/TextField";


export const CreateOfferTitle = styled(Typography)` export const CreateOfferTitle = styled(Typography)`
cursor: auto; cursor: auto;
letter-spacing: 0.2px; letter-spacing: 0.2px;
} }
@media (max-width: 600px) {
& label {
font-size: 9px;
}
}
`; `;
export const DescriptionField = styled(TextField)` export const DescriptionField = styled(TextField)`
margin-bottom: 4px; 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)` export const NextButton = styled(PrimaryButton)`
margin-top: 16px; margin-top: 16px;
width: 100%;

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


@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
width: 332px;
position: absolute; 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 Datei anzeigen

import React, { useState, useEffect } from "react";
import React, { useMemo, useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import {
CreateOfferFormContainer, CreateOfferFormContainer,
} from "./SecondPartCreateOffer.styled"; } from "./SecondPartCreateOffer.styled";
import ImagePicker from "../../../ImagePicker/ImagePicker"; import ImagePicker from "../../../ImagePicker/ImagePicker";
import { useTranslation, Trans } from "react-i18next"; import { useTranslation, Trans } from "react-i18next";
import Option from "../../../Select/Option/Option";
import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled"; 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 selectedTheme from "../../../../themes";
import { conditionSelectEnum } from "../../../../enums/conditionEnum"; import { conditionSelectEnum } from "../../../../enums/conditionEnum";
import { useFormik } from "formik"; import { useFormik } from "formik";
}; };
const { t } = useTranslation(); 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) => { const handleSubmit = (values) => {
props.handleNext(values); props.handleNext(values);
console.log("slike", images); console.log("slike", images);


return ( 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) => ( ? images.map((item, index) => (
<ImagePicker <ImagePicker
key={index} key={index}
showDeleteIcon 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 Datei anzeigen

import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import {
CreateOfferFormContainer,
// CreateOfferFormContainer,
PreviewCard, PreviewCard,
} from "./ThirdPartCreateOffer.styled"; } 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 ThirdPartCreateOffer = (props) => {
const {t} = useTranslation();
const offer = { const offer = {
offer: { offer: {
category: { category: {
}; };


return ( 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 Datei anzeigen

import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import selectedTheme from "../../../themes"; import selectedTheme from "../../../themes";
import useFilters from "../../../hooks/useFilters"; import useFilters from "../../../hooks/useFilters";
import HeaderBack from "../../ItemDetails/Header/Header";


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


useEffect(() => { useEffect(() => {
if (!filters.selectedCategory || filters.selectedCategory?._id === 0) { if (!filters.selectedCategory || filters.selectedCategory?._id === 0) {
}; };


return ( return (
<FilterCardContainer responsiveOpen={props.responsiveOpen} responsive={props.responsive}>
<FilterCardContainer
responsiveOpen={props.responsiveOpen}
responsive={props.responsive}
myOffers={props.myOffers}
>
{props.myOffers && <HeaderBack />}
<Header> <Header>
<Title>{t("filters.title")}</Title> <Title>{t("filters.title")}</Title>
<Link <Link
fontWeight: "600", fontWeight: "600",
fontSize: "12px", fontSize: "12px",
border: "0", border: "0",
textAlign: "center"
textAlign: "center",
}} }}
> >
ZATVORI ZATVORI
responsive: PropTypes.bool, responsive: PropTypes.bool,
responsiveOpen: PropTypes.bool, responsiveOpen: PropTypes.bool,
closeResponsive: PropTypes.func, closeResponsive: PropTypes.func,
myOffers: PropTypes.bool,
}; };


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


export default FilterCard; export default FilterCard;

+ 13
- 4
src/components/Cards/FilterCard/FilterCard.styled.js Datei anzeigen

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

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

@media (max-width: 900px) { @media (max-width: 900px) {
margin-left: -400px; margin-left: -400px;
${(props) => ${(props) =>
width: 100vw; width: 100vw;
bottom: 0; bottom: 0;
height: calc(100% - 50px); height: calc(100% - 50px);
` : "display: none"};
`
: "display: none"};
transition: all ease-in-out 0.36s; transition: all ease-in-out 0.36s;

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

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js Datei anzeigen

}, [props.filters]) }, [props.filters])


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

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js Datei anzeigen

} }
}, [props.selected]) }, [props.selected])


console.log(props.selected)


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

+ 46
- 26
src/components/Cards/ItemDetailsCard/ItemDetailsCard.js Datei anzeigen

import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import {
CheckButton, CheckButton,
OfferDetails, OfferDetails,
OfferImage, OfferImage,
Scroller, Scroller,
PublishButtonContainer,
} from "./ItemDetailsCard.styled"; } from "./ItemDetailsCard.styled";
import { NextButton } from "../CreateOfferCard/FirstPart/FirstPartCreateOffer.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg"; import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";
import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg"; import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import selectedTheme from "../../../themes"; 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 ItemDetailsCard = (props) => {
const offer = props.offer; 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); const dateCreated = new Date(offer?.offer?._created);

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

const dayCreated = const dayCreated =
dateCreated.getDate() < 10 dateCreated.getDate() < 10
? "0" + dateCreated.getDate() ? "0" + dateCreated.getDate()
? "0" + (dateCreated.getMonth() + 1) ? "0" + (dateCreated.getMonth() + 1)
: dateCreated.getMonth() + 1; : dateCreated.getMonth() + 1;
const yearCreated = dateCreated.getFullYear(); 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 ( return (
<ItemDetailsCardContainer <ItemDetailsCardContainer
sponsored={props.sponsored.toString()} sponsored={props.sponsored.toString()}
</InfoIcon> </InfoIcon>
<InfoText>{offer?.offer?.condition}</InfoText> <InfoText>{offer?.offer?.condition}</InfoText>
</InfoGroup> </InfoGroup>
{!props.showPublishButton && (
{!props.hideViews && (
<InfoGroup views> <InfoGroup views>
<InfoIcon color={"black"} component="span" size="12px" last> <InfoIcon color={"black"} component="span" size="12px" last>
<Eye width={"18px"} height={"20px"} /> <Eye width={"18px"} height={"20px"} />
</InfoIcon> </InfoIcon>
<InfoText>{offer?.offer?.views?.viewers?.length}</InfoText>
<InfoText>{offer?.offer?.views?.count}</InfoText>
</InfoGroup> </InfoGroup>
)} )}
</Info> </Info>
{dayCreated}.{monthCreated}.{yearCreated} {dayCreated}.{monthCreated}.{yearCreated}
</PostDate> </PostDate>
</OfferInfo> </OfferInfo>
<Details hasScrollBar={!props.showPublishButton}>
<Details
hasScrollBar={!props.showPublishButton}
exchange={props.showExchangeButton}
>
<OfferTitle>{offer?.offer?.name}</OfferTitle> <OfferTitle>{offer?.offer?.name}</OfferTitle>
<Scroller> <Scroller>
{offer?.offer?.images?.map((item) => { {offer?.offer?.images?.map((item) => {
</Scroller> </Scroller>
<OfferDetails> <OfferDetails>
<OfferDescriptionTitle>Opis:</OfferDescriptionTitle> <OfferDescriptionTitle>Opis:</OfferDescriptionTitle>
<OfferDescriptionText showBarterButton={props.showBarterButton}>
<OfferDescriptionText showBarterButton={props.showExchangeButton}>
{offer?.offer?.description} {offer?.offer?.description}
</OfferDescriptionText> </OfferDescriptionText>
</OfferDetails> </OfferDetails>
</Details> </Details>
{!props.halfwidth && !props.showPublishButton ? (
{!props.halfwidth && props.showExchangeButton ? (
<React.Fragment> <React.Fragment>
<CheckButton <CheckButton
variant={props.sponsored ? "contained" : "outlined"} variant={props.sponsored ? "contained" : "outlined"}
buttoncolor={selectedTheme.primaryPurple} buttoncolor={selectedTheme.primaryPurple}
textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple} textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple}
style={{ fontWeight: "600" }} style={{ fontWeight: "600" }}
onClick={startExchange}
> >
Trampi Trampi
</CheckButton> </CheckButton>
) : ( ) : (
<></> <></>
)} )}
{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> </ItemDetailsCardContainer>
); );
}; };
halfwidth: PropTypes.bool, halfwidth: PropTypes.bool,
sponsored: PropTypes.bool, sponsored: PropTypes.bool,
offer: PropTypes.any, offer: PropTypes.any,
hideViews: PropTypes.bool,
showExchangeButton: PropTypes.bool,
// offer: PropTypes.shape({ // offer: PropTypes.shape({
// images: PropTypes.any, // images: PropTypes.any,
// name:PropTypes.string, // name:PropTypes.string,
ItemDetailsCard.defaultProps = { ItemDetailsCard.defaultProps = {
halfwidth: false, halfwidth: false,
sponsored: false, sponsored: false,
showExchangeButton: true,
}; };


export default ItemDetailsCard; export default ItemDetailsCard;

+ 7
- 4
src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js Datei anzeigen

font-size: 16px; font-size: 16px;
color: ${selectedTheme.primaryDarkText}; color: ${selectedTheme.primaryDarkText};
line-height: 22px; line-height: 22px;
max-width: calc(100% - 130px);
padding-bottom: 20px; padding-bottom: 20px;
max-width: ${(props) => props.showBarterButton ? "calc(100% - 230px)" : "100%"};
@media (max-width: 600px) { @media (max-width: 600px) {
font-size: 14px; font-size: 14px;
max-width: 100%; max-width: 100%;
max-height: 100px;
} }
/* max-width: calc(100% - 230px); */ /* max-width: calc(100% - 230px); */
max-width: ${(props) => props.showBarterButton && "calc(100% - 230px)"};
/* overflow: hidden; */ /* overflow: hidden; */
/* display: -webkit-box; /* display: -webkit-box;
-webkit-line-clamp: 5; -webkit-line-clamp: 5;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
${(props) => props.hasScrollBar && `max-height: 400px;`}
${(props) => props.hasScrollBar && !props.exchange && `height: 300px;`}
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
::-webkit-scrollbar { ::-webkit-scrollbar {


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

+ 28
- 0
src/components/Cards/LittleOfferCard/LittleOfferCard.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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


const closeModalHandler = () => { const closeModalHandler = () => {
setDeleteOfferModal(false); setDeleteOfferModal(false);
document.body.style.overflow = "auto"; document.body.style.overflow = "auto";
} }


console.log(props.offer);
return ( return (
<React.Fragment> <React.Fragment>
<OfferCardContainer <OfferCardContainer
vertical={props.vertical} vertical={props.vertical}
sponsored={ 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} halfwidth={props.halfwidth ? 1 : 0}
> >
<OfferFlexContainer vertical={props.vertical}> <OfferFlexContainer vertical={props.vertical}>
<OfferImageContainer vertical={props.vertical}> <OfferImageContainer vertical={props.vertical}>
<OfferImage <OfferImage
src={props?.offer?.images[0]}
src={props?.offer?.images ? props?.offer?.images[0] : ""}
vertical={props.vertical} vertical={props.vertical}
></OfferImage> ></OfferImage>
</OfferImageContainer> </OfferImageContainer>
<DetailIcon color="black" component="span" size="16px"> <DetailIcon color="black" component="span" size="16px">
<Category width={"14px"} /> <Category width={"14px"} />
</DetailIcon> </DetailIcon>
<DetailText>{props?.offer?.category.name}</DetailText>
<DetailText>{props?.offer?.category?.name}</DetailText>
</OfferCategory> </OfferCategory>
<OfferViews vertical={props.vertical}>
{props.dontShowViews ? (<></>) : (<OfferViews vertical={props.vertical}>
<DetailIcon color="black" component="span" size="16px"> <DetailIcon color="black" component="span" size="16px">
<EyeIcon /> <EyeIcon />
</DetailIcon> </DetailIcon>
<DetailText>{props?.offer?.views?.viewers?.length}</DetailText>
</OfferViews>
<DetailText>{props?.offer?.views?.count}</DetailText>
</OfferViews>)}
</OfferDetails> </OfferDetails>
</OfferInfo> </OfferInfo>
{!props.halfwidth ? ( {!props.halfwidth ? (
<EditIcon /> <EditIcon />
</EditIconContainer> </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 /> <Message />
</MessageIcon> </MessageIcon>
)} )}
pinned: PropTypes.bool, pinned: PropTypes.bool,
vertical: PropTypes.bool, vertical: PropTypes.bool,
isMyOffer: PropTypes.bool, isMyOffer: PropTypes.bool,
aboveChat: PropTypes.bool,
disabledReviews: PropTypes.bool,
messageUser: PropTypes.func,
makeReview: PropTypes.func,
dontShowViews: PropTypes.bool,
}; };
OfferCard.defaultProps = { OfferCard.defaultProps = {
halfwidth: false, halfwidth: false,
sponsored: false, sponsored: false,
messageUser: () => {},
makeReview: () => {},
}; };


export default OfferCard; export default OfferCard;

+ 18
- 7
src/components/Cards/OfferCard/OfferCard.styled.js Datei anzeigen

import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg"; import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg";
import { ReactComponent as Edit } from "../../../assets/images/svg/edit.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)` export const OfferCardContainer = styled(Container)`
display: flex; display: flex;
export const RemoveIcon = styled(Remove)``; export const RemoveIcon = styled(Remove)``;
export const EditIconContainer = styled(MessageIcon)` export const EditIconContainer = styled(MessageIcon)`
right: 70px; right: 70px;

@media screen and (max-width: 600px) {
position: absolute;
display: block;
right: 20px;
top: 60%;
}
`; `;
export const EditIcon = styled(Edit)``; 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 Datei anzeigen


+ 132
- 0
src/components/Cards/UserReviewsCard/UserReviewsCard.js Datei anzeigen

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 Datei anzeigen

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


export const ReviewsBox = styled(Box)` export const ReviewsBox = styled(Box)`
width: 100%; width: 100%;
height: calc(100% - 90px); height: calc(100% - 90px);
max-height: 100vh;
@media (max-width: 1200px) { @media (max-width: 1200px) {
padding: 0 50px;
padding: 0;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
position: relative; position: relative;
width: 100%; width: 100%;
margin-bottom: 36px; 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 Datei anzeigen

import React, {useState, useEffect} from "react";
import React, { useState, useEffect } from "react";
import ChatCard from "../Cards/ChatCard/ChatCard"; import ChatCard from "../Cards/ChatCard/ChatCard";
import Header from "../ItemDetails/Header/Header";
import { HeaderSelect, SelectOption } from "../MarketPlace/Header/Header.styled";
import { import {
ChatColumnContainer, ChatColumnContainer,
HeaderBack,
HeaderSelect,
ListContainer, ListContainer,
ListHeader, ListHeader,
SelectOption,
TitleSortContainer, TitleSortContainer,
} from "./ChatColumn.styled"; } from "./ChatColumn.styled";
import { sortEnum } from "../../enums/sortEnum"; import { sortEnum } from "../../enums/sortEnum";
import { Grid } from "@mui/material"; import { Grid } from "@mui/material";
import MailOutlineIcon from "@mui/icons-material/MailOutline"; import MailOutlineIcon from "@mui/icons-material/MailOutline";
import { HeaderTitle } from "../ProfileCard/ProfileCard.styled"; 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) => { export const DownArrow = (props) => {
<IconStyled {...props}>
<Down/>
</IconStyled>
}
<IconStyled {...props}>
<Down />
</IconStyled>;
};


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

+ 55
- 11
src/components/ChatColumn/ChatColumn.styled.js Datei anzeigen

import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { Container } from "@mui/system"; import { Container } from "@mui/system";
import styled from "styled-components"; 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)` export const ChatColumnContainer = styled(Container)`

`;
margin-bottom: 40px;
`;


export const ListContainer = styled(Box)` 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)` export const ListHeader = styled(Box)`
${(props) =>
${(props) =>
props.vertical && props.vertical &&
` `
position: absolute; position: absolute;
`; `;


export const TitleSortContainer = 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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

import { Box } from "@mui/material";
import styled from "styled-components";

export const DirectChatContainer = styled(Box)`
`

+ 62
- 0
src/components/DirectChat/DirectChatContent/DirectChatContent.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

import { Box } from "@mui/material";
import styled from "styled-components";

export const DirectChatHeaderContainer = styled(Box)`

`

+ 20
- 0
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

import React, { useState, useMemo, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef } from "react";
import { import {
AddOfferButton, AddOfferButton,
AuthButtonsContainer, AuthButtonsContainer,
AuthButtonsDrawerContainer,
DrawerContainer,
EndIcon, EndIcon,
FilterContainer, FilterContainer,
FilterIcon, FilterIcon,
UserName, UserName,
} from "./Header.styled"; } from "./Header.styled";
import PropTypes from "prop-types"; 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 { useTheme } from "@mui/system";
import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined"; import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
import MailIcon from "@mui/icons-material/EmailOutlined"; import MailIcon from "@mui/icons-material/EmailOutlined";
import Autorenew from "@mui/icons-material/Autorenew"; import Autorenew from "@mui/icons-material/Autorenew";
import AccountCircle from "@mui/icons-material/PersonOutlineOutlined"; import AccountCircle from "@mui/icons-material/PersonOutlineOutlined";
import Drawer from "../MUI/DrawerComponent"; import Drawer from "../MUI/DrawerComponent";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import PopoverComponent from "../Popovers/PopoverComponent"; import PopoverComponent from "../Popovers/PopoverComponent";
import { MyPosts } from "../Popovers/MyPosts/MyPosts"; import { MyPosts } from "../Popovers/MyPosts/MyPosts";
import { MyMessages } from "../Popovers/MyMessages/MyMessages"; import { MyMessages } from "../Popovers/MyMessages/MyMessages";
import { useSearch } from "../../hooks/useSearch"; import { useSearch } from "../../hooks/useSearch";
import { selectProfileName } from "../../store/selectors/profileSelectors"; import { selectProfileName } from "../../store/selectors/profileSelectors";
import { useHistory, useRouteMatch } from "react-router-dom"; 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 useFilters from "../../hooks/useFilters";
import FilterCard from "../Cards/FilterCard/FilterCard"; import FilterCard from "../Cards/FilterCard/FilterCard";
import { useQueryString } from "../../hooks/useQueryString"; import { useQueryString } from "../../hooks/useQueryString";
import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers"; import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers";
import { fetchMineProfile } from "../../store/actions/profile/profileActions"; import { fetchMineProfile } from "../../store/actions/profile/profileActions";
import CreateOffer from "../Cards/CreateOfferCard/CreateOffer"; 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 [openFilters, setOpenFilters] = useState(false);
const [showSearchBar, setShowSearchBar] = useState(true); const [showSearchBar, setShowSearchBar] = useState(true);
const [numberOfFilters, setNumberOfFilters] = useState(0); const [numberOfFilters, setNumberOfFilters] = useState(0);
const filters = useFilters(); const filters = useFilters();
const searchMobileRef = useRef(null); const searchMobileRef = useRef(null);
const queryStringHook = useQueryString(); const queryStringHook = useQueryString();
const [openDrawer, setOpenDrawer] = useState(false);

useEffect(() => { useEffect(() => {
dispatch(fetchMineProfile());
dispatch(fetchMineProfile());
}, []); }, []);
useEffect(() => { useEffect(() => {
setUserPopoverOpen(false); setUserPopoverOpen(false);
} else { } else {
setShowSearchBar(true); setShowSearchBar(true);
} }
}, [history.location.pathname])
}, [history.location.pathname]);
useEffect(() => { useEffect(() => {
setNumberOfFilters(filters.calculateFiltersChosen()); setNumberOfFilters(filters.calculateFiltersChosen());
}, [ }, [


useEffect(() => { useEffect(() => {
let shouldShowHeader = true; let shouldShowHeader = true;
console.log(props);
if ( 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 === "/" location.pathname === "/"
) { ) {
shouldShowHeader = false; shouldShowHeader = false;
setUserPopoverOpen(false); setUserPopoverOpen(false);
setMsgPopoverOpen(false); setMsgPopoverOpen(false);
setPostsPopoverOpen(false); setPostsPopoverOpen(false);
}, [location.pathname])

const handleToggleDrawer = () => {
setOpenDrawer(!openDrawer);
};
}, [location.pathname]);
const handleNavigateLogin = () => { const handleNavigateLogin = () => {
setShouldShow(false); setShouldShow(false);
history.push(LOGIN_PAGE); history.push(LOGIN_PAGE);
setShouldShow(false); setShouldShow(false);
history.push(REGISTER_PAGE); 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; let listener;
const handleFocusSearch = () => { const handleFocusSearch = () => {
setOpenFilters((prevState) => !prevState); setOpenFilters((prevState) => !prevState);
}; };


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

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


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

+ 11
- 10
src/components/Header/Header.styled.js Datei anzeigen

flex-direction: column; 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)` export const LogoContainer = styled(Box)`
display: flex; display: flex;
justify-content: center; justify-content: center;
font-weight: 600; font-weight: 600;
white-space: nowrap; 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)` export const RegisterButton = styled(PrimaryButton)`
height: 49px; height: 49px;
width: 180px; width: 180px;

+ 6
- 0
src/components/ImagePicker/ImagePicker.styled.js Datei anzeigen

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

+ 9
- 10
src/components/ItemDetails/Header/Header.js Datei anzeigen

import React from 'react';
import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
//import { IconButton } from "../../Buttons/IconButton/IconButton"; //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) => ( // const DownArrow = (props) => (
// <IconStyled {...props}> // <IconStyled {...props}>
// </IconStyled> // </IconStyled>
// ); // );


const Header = () => {

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


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


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

+ 19
- 23
src/components/ItemDetails/ItemDetails.js Datei anzeigen

import React, { useMemo } from 'react';
import React, { useMemo } from "react";
import Header from "./Header/Header"; import Header from "./Header/Header";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { ItemDetailsContainer } from "./ItemDetails.styled"; import { ItemDetailsContainer } from "./ItemDetails.styled";
import ItemDetailsCard from "../Cards/ItemDetailsCard/ItemDetailsCard"; import ItemDetailsCard from "../Cards/ItemDetailsCard/ItemDetailsCard";
import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard"; 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 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; export default ItemDetails;

+ 20
- 12
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js Datei anzeigen

import { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg"; import { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg";
import selectedTheme from "../../../themes"; import selectedTheme from "../../../themes";
import { useHistory } from "react-router-dom"; 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 ItemDetailsHeaderCard = (props) => {
const history = useHistory(); const history = useHistory();
const chats = useSelector(selectLatestChats);
const offer = props.offer; const offer = props.offer;
const userId = useSelector(selectUserId);
if (!props.offer) { if (!props.offer) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
const handleGoProfile = () => { const handleGoProfile = () => {
history.push(`/profile/${offer?.offer?.userId}`); 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 ( return (
<ItemDetailsHeaderContainer <ItemDetailsHeaderContainer
isMyProfile={props.isMyProfile} isMyProfile={props.isMyProfile}
<UserIcon /> <UserIcon />
</UserIconContainer> </UserIconContainer>
) : ( ) : (
<MessageIcon>
<MessageIcon onClick={() => messageUser(offer)}>
<MessageColor /> <MessageColor />
</MessageIcon> </MessageIcon>
)} )}
sponsored: PropTypes.bool, sponsored: PropTypes.bool,
offer: PropTypes.any, offer: PropTypes.any,
isMyProfile: PropTypes.bool, 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 = { ItemDetailsHeaderCard.defaultProps = {
halfwidth: false, halfwidth: false,

+ 16
- 9
src/components/MarketPlace/Header/Header.js Datei anzeigen

HeaderOptions, HeaderOptions,
HeaderSelect, HeaderSelect,
IconStyled, IconStyled,
MySwapsTitle,
RefreshIcon,
SelectOption, SelectOption,
} from "./Header.styled"; } from "./Header.styled";
import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg"; import { ReactComponent as GridSquare } from "../../../assets/images/svg/offer-grid-square.svg";
for (const sortOption in sortEnum) { for (const sortOption in sortEnum) {
if (sortEnum[sortOption].value === event.target.value) { if (sortEnum[sortOption].value === event.target.value) {
chosenOption = sortEnum[sortOption]; chosenOption = sortEnum[sortOption];
sorting.changeSorting(chosenOption)
sorting.changeSorting(chosenOption);
} }
} }
}; };
return ( return (
<HeaderContainer> <HeaderContainer>
<Tooltip title={headerString}> <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> </Tooltip>
<HeaderOptions> <HeaderOptions>
isGrid: PropTypes.bool, isGrid: PropTypes.bool,
filters: PropTypes.any, filters: PropTypes.any,
category: PropTypes.string, category: PropTypes.string,
myOffers: PropTypes.bool,
}; };
Header.defaultProps = { Header.defaultProps = {
isGrid: false, isGrid: false,

+ 18
- 0
src/components/MarketPlace/Header/Header.styled.js Datei anzeigen

import { IconButton } from "../../Buttons/IconButton/IconButton"; import { IconButton } from "../../Buttons/IconButton/IconButton";
import Option from "../../Select/Option/Option"; import Option from "../../Select/Option/Option";
import Select from "../../Select/Select"; import Select from "../../Select/Select";
import {ReactComponent as Refresh} from "../../../assets/images/svg/refresh.svg"


export const HeaderContainer = styled(Box)` export const HeaderContainer = styled(Box)`
margin-top: 20px; margin-top: 20px;
display: none; 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 Datei anzeigen

import Header from "./Header/Header"; import Header from "./Header/Header";
import Offers from "./Offers/Offers"; import Offers from "./Offers/Offers";


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


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


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


export default MarketPlace; export default MarketPlace;

+ 177
- 23
src/components/MarketPlace/Offers/Offers.js Datei anzeigen

import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { OffersContainer } from "./Offers.styled"; import { OffersContainer } from "./Offers.styled";
import OfferCard from "../../Cards/OfferCard/OfferCard"; 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 { useDispatch, useSelector } from "react-redux";
import { import {
selectMineOffers,
selectOffers, selectOffers,
selectPinnedOffers, selectPinnedOffers,
selectTotalOffers, selectTotalOffers,
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { useQueryString } from "../../../hooks/useQueryString"; import { useQueryString } from "../../../hooks/useQueryString";
import OffersNotFound from "./OffersNotFound"; 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 Offers = (props) => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
// const [pinnedLength, setPinnedLength] = useState(0);
const pinnedOffers = useSelector(selectPinnedOffers); 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 offers = useSelector(selectOffers);
const mineOffers = useSelector(selectMineOffers);
const chats = useSelector(selectLatestChats);
const total = useSelector(selectTotalOffers); const total = useSelector(selectTotalOffers);
const history = useHistory(); const history = useHistory();
const dispatch = useDispatch(); const dispatch = useDispatch();
const offersRef = useRef(null); const offersRef = useRef(null);
const queryStringHook = useQueryString();
const queryStringHook = useQueryString(props.myOffers);
const userId = useSelector(selectUserId);

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

useEffect(() => { useEffect(() => {
let queryObject = queryStringHook.getQueryObject(); let queryObject = queryStringHook.getQueryObject();
if (queryObject.page && queryObject.page !== 1) { if (queryObject.page && queryObject.page !== 1) {
}, [history.location.search]); }, [history.location.search]);


useEffect(() => { useEffect(() => {
if (history?.location?.state?.logo) {
if (history?.location?.state?.logo || history?.location?.state?.refetch) {
dispatch(fetchOffers({ queryString: "" })); dispatch(fetchOffers({ queryString: "" }));
setPage(1); setPage(1);
history.location.state = undefined; history.location.state = undefined;


useEffect(() => { useEffect(() => {
if (queryStringHook.loadedFromURL) { 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 { } else {
queryStringHook.appendMultipleToQueryString([ queryStringHook.appendMultipleToQueryString([
{ key: "size", value: "10" }, { key: "size", value: "10" },
if (queryObject.has("page")) { if (queryObject.has("page")) {
if (queryObject.get("page") !== page.toString()) { if (queryObject.get("page") !== page.toString()) {
queryStringHook.appendToQueryString("page", page); queryStringHook.appendToQueryString("page", page);
} else {
refetch();
} }
} else { } else {
queryStringHook.appendToQueryString("page", page); queryStringHook.appendToQueryString("page", 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) => { const handleDifferentPage = (pageNum) => {
setPage(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 ( return (
<> <>
{offers.length === 0 ? ( {offers.length === 0 ? (
) : ( ) : (
<OffersContainer ref={offersRef}> <OffersContainer ref={offersRef}>
<> <>
{pinnedOffers != undefined &&
pinnedOffers.map((item) => {
{allOffersToShow != undefined &&
allOffersToShow.map((item) => {
return ( return (
<OfferCard <OfferCard
key={item._id} key={item._id}
offer={item} offer={item}
halfwidth={props.isGrid} halfwidth={props.isGrid}
messageUser={messageOneUser}
/> />
); );
})} })}
); );
})} })}
<Paging <Paging
totalElements={total}
totalElements={totalOffers}
elementsPerPage={10} elementsPerPage={10}
current={page} current={page}
changePage={handleDifferentPage} changePage={handleDifferentPage}
Offers.propTypes = { Offers.propTypes = {
children: PropTypes.node, children: PropTypes.node,
isGrid: PropTypes.bool, isGrid: PropTypes.bool,
myOffers: PropTypes.bool,
};

Offers.defaultProps = {
myOffers: false,
}; };


export default Offers; export default Offers;

+ 5
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.styled.js Datei anzeigen

cursor: pointer; cursor: pointer;
} }
& p { & p {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
max-height: 32px;
font-size: 0.81rem; font-size: 0.81rem;
& svg { & svg {
position: relative; position: relative;

+ 18
- 11
src/components/Popovers/MyMessages/MyMessages.js Datei anzeigen

import { selectUserId } from "../../../store/selectors/loginSelectors"; import { selectUserId } from "../../../store/selectors/loginSelectors";
import HeaderPopover from "../HeaderPopover/HeaderPopover"; 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 = () => { export const MyMessages = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
const [lastChats, setLastChats] = useState([]); 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(() => { useEffect(() => {
if (userId?.length > 1) {
if (userId?.length > 1 && chats?.length === 0) {
dispatch(fetchHeaderChats(userId)); dispatch(fetchHeaderChats(userId));
} }
}, [userId]); }, [userId]);
}, [chats]); }, [chats]);
const goToMessages = () => { const goToMessages = () => {
history.push(CHAT_PAGE); history.push(CHAT_PAGE);
}
};
return ( return (
<HeaderPopover <HeaderPopover
title={t("header.myMessages")} title={t("header.myMessages")}

+ 5
- 0
src/components/Popovers/MyPosts/MyPosts.js Datei anzeigen

import { fetchMineOffers } from "../../../store/actions/offers/offersActions"; import { fetchMineOffers } from "../../../store/actions/offers/offersActions";
import { selectProfileName } from "../../../store/selectors/profileSelectors"; import { selectProfileName } from "../../../store/selectors/profileSelectors";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { MY_OFFERS_PAGE } from "../../../constants/pages";


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

+ 1
- 1
src/components/Popovers/MyProfile/MyProfile.js Datei anzeigen

setProfileAsArray([ setProfileAsArray([
{ {
alt: "Profile", alt: "Profile",
src: `${profile.image}`,
src: profile.image,
title: profile.company.name, title: profile.company.name,
onClick: () => seeMyProfile(), onClick: () => seeMyProfile(),
text: ( text: (

+ 21
- 2
src/components/Profile/ProfileOffers/ProfileOffers.js Datei anzeigen

import { useRef } from "react"; import { useRef } from "react";
import { selectProfileOffers } from "../../../store/selectors/offersSelectors"; import { selectProfileOffers } from "../../../store/selectors/offersSelectors";
import useScreenDimensions from "../../../hooks/useScreenDimensions"; 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 ProfileOffers = (props) => {
const [sortOption, setSortOption] = useState(sortEnum.INITIAL); const [sortOption, setSortOption] = useState(sortEnum.INITIAL);
const [offersToShow, setOffersToShow] = useState([]); const [offersToShow, setOffersToShow] = useState([]);
const searchRef = useRef(null); const searchRef = useRef(null);
const chats = useSelector(selectLatestChats);
const profileOffers = useSelector(selectProfileOffers); const profileOffers = useSelector(selectProfileOffers);
const dimensions = useScreenDimensions(); const dimensions = useScreenDimensions();
const history = useHistory();
const { t } = useTranslation(); 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(() => { useEffect(() => {
let newOffersToShow = [...offersToShow]; let newOffersToShow = [...offersToShow];
<OffersContainer> <OffersContainer>
{dimensions.width > 600 ? ( {dimensions.width > 600 ? (
offersToShow.map((item) => ( 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> <OffersScroller hideArrows>
{offersToShow.map((item) => ( {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> </OffersScroller>
)} )}
</OffersContainer> </OffersContainer>

+ 13
- 10
src/components/ProfileCard/ProfileCard.js Datei anzeigen

MessageIcon, MessageIcon,
MessageButton, MessageButton,
} from "./ProfileCard.styled"; } from "./ProfileCard.styled";

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

import PersonOutlineIcon from "@mui/icons-material/PersonOutline"; import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
import { useRouteMatch } from "react-router-dom"; import { useRouteMatch } from "react-router-dom";
import { fetchProfile } from "../../store/actions/profile/profileActions"; import { fetchProfile } from "../../store/actions/profile/profileActions";
import { useState } from "react"; import { useState } from "react";
import { fetchProfileOffers } from "../../store/actions/offers/offersActions"; import { fetchProfileOffers } from "../../store/actions/offers/offersActions";
import EditProfile from "./EditProfile"; import EditProfile from "./EditProfile";
import { useTranslation } from "react-i18next";


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

useEffect(() => { useEffect(() => {
if (idProfile?.length > 0) { if (idProfile?.length > 0) {
reFetchProfile(); reFetchProfile();
document.body.style.overflow = "auto"; document.body.style.overflow = "auto";
} }


console.log(profile);
return ( return (
<> <>
<ProfileCardContainer> <ProfileCardContainer>
sx={{ mb: 1.4 }} sx={{ mb: 1.4 }}
> >
<PersonOutlineIcon color="action" sx={{ mr: 0.9 }} /> <PersonOutlineIcon color="action" sx={{ mr: 0.9 }} />
<HeaderTitle>Moj Profil</HeaderTitle>
<HeaderTitle>{t("profile.myProfile")}</HeaderTitle>
</Grid> </Grid>
<ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}> <ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}>
{isMyProfile ? ( {isMyProfile ? (
> >
<PocketIcon /> <PocketIcon />
<ProfilePIB isMyProfile={isMyProfile} variant="subtitle2"> <ProfilePIB isMyProfile={isMyProfile} variant="subtitle2">
PIB: {profile?.company?.PIB}
{t("profile.PIB")} {profile?.company?.PIB}
</ProfilePIB> </ProfilePIB>
</ProfilePIBContainer> </ProfilePIBContainer>
</Grid> </Grid>
sx={{ width: "fit-content" }} sx={{ width: "fit-content" }}
> >
<StatsItem variant="subtitle2"> <StatsItem variant="subtitle2">
<b>{profile?.statistics?.publishes?.count}</b> objava
<b>{profile?.statistics?.publishes?.count}</b>
{t("profile.publishes")}
</StatsItem> </StatsItem>


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

+ 1
- 1
src/components/Router/PrivateRoute.js Datei anzeigen

const isUserAuthenticated = useMemo(() => { const isUserAuthenticated = useMemo(() => {
if (userId?.length === 0) return false; if (userId?.length === 0) return false;
return true; return true;
})
}, [userId])


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

+ 27
- 0
src/components/UserReviews/NoReviews/NoReviews.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen


src/components/UserReviewsCard/UserReviewsSkeleton/UserReviewsSkeleton.styled.js → src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.styled.js Datei anzeigen

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


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

+ 94
- 0
src/components/UserReviews/UserReviews.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

export const ITEM_DETAILS_PAGE = "/proizvodi/:idProizvod"; export const ITEM_DETAILS_PAGE = "/proizvodi/:idProizvod";
export const PROFILE_PAGE = "/profile/:idProfile" export const PROFILE_PAGE = "/profile/:idProfile"
export const CHAT_PAGE = "/messages"; 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 Datei anzeigen

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 Datei anzeigen







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

+ 6
- 7
src/hooks/useQueryString.js Datei anzeigen

/* eslint-disable */
import _ from "lodash"; import _ from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
// import _ from "lodash"
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { HOME_PAGE } from "../constants/pages";
import { setQueryString as setQueryStringSaga } from "../store/actions/queryString/queryStringActions"; import { setQueryString as setQueryStringSaga } from "../store/actions/queryString/queryStringActions";
import { selectQueryString } from "../store/selectors/queryStringSelectors"; 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"; import { convertQueryStringBackend, convertQueryStringFrontend } from "../util/helpers/queryHelpers";


export const useQueryString = () => { export const useQueryString = () => {
}, [queryString, loadedFromURL]); }, [queryString, loadedFromURL]);


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

+ 14
- 11
src/hooks/useSorting.js Datei anzeigen

let queryObject = new URLSearchParams( let queryObject = new URLSearchParams(
convertQueryStringFrontend(queryString) convertQueryStringFrontend(queryString)
); );
if (queryObject.has("sortBy"))
if (queryObject.has("sortBy")) {
if (queryObject.get("sortBy") === "newest") { if (queryObject.get("sortBy") === "newest") {
setSelectedSortOption(sortEnum.NEW); 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]); }, [queryStringHook.queryString, queryStringHook.loadedFromURL]);
} }
if (_des_popular !== null) { if (_des_popular !== null) {
queryArray.push({ key: "_des_popular", value: `${_des_popular}` }); queryArray.push({ key: "_des_popular", value: `${_des_popular}` });
queryArray.push({ key: "_des_date" })
queryArray.push({ key: "_des_date" });
} }
if (shouldGoFirstPage) { if (shouldGoFirstPage) {
queryArray.push({key: "page", value: "1"});
queryArray.push({ key: "page", value: "1" });
} }
queryStringHook.appendMultipleToQueryString(queryArray); queryStringHook.appendMultipleToQueryString(queryArray);
}; };


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


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

+ 26
- 0
src/i18n/resources/rs.js Datei anzeigen

labelPhone: "Telefon", labelPhone: "Telefon",
labelLocation: "Lokacija", labelLocation: "Lokacija",
labelWebsite: "Adresa Websajta", labelWebsite: "Adresa Websajta",
logout: "Odjavi se",
next: "Sledeće", next: "Sledeće",
nextPage: "Sledeća strana", nextPage: "Sledeća strana",
previousPage: "Prethodna strana", previousPage: "Prethodna strana",
supportedImagesFormats: supportedImagesFormats:
"Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>", "Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>",
continue: "NASTAVI", continue: "NASTAVI",
publish: "OBJAVI",
}, },
apiErrors: { apiErrors: {
somethingWentWrong: "Greska sa serverom!", somethingWentWrong: "Greska sa serverom!",
checkEverything: "POGLEDAJ SVE", checkEverything: "POGLEDAJ SVE",
myMessages: "Moje poruke", myMessages: "Moje poruke",
newOffers: "Najnovije ponude", newOffers: "Najnovije ponude",
navMenu: "Navigacioni Meni",
}, },
reviews: { reviews: {
title: "Ova kompanija još uvek nema ocenu.", title: "Ova kompanija još uvek nema ocenu.",
altTitle: "Budite prvi da je ocenite", 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: { editProfile: {
website: "Web Sajt*", website: "Web Sajt*",
"Nažalost ne postoji ni jedna objava <br /> za unete kriterijume.", "Nažalost ne postoji ni jedna objava <br /> za unete kriterijume.",
showAllOffers: "Pogledaj sve objave", 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 Datei anzeigen

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) => { 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} {props.content}
</Content>
<RightCard item xs={0} lg={3} xl={2.4} md={4} >
{props.rightCard}
</RightCard>
</Grid>
</ChatGridLayoutContainer>
)
}
</GridContent>
</GridContainer>
</ChatGridLayoutContainer>
);
};


ChatGridLayout.propTypes = { 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 Datei anzeigen

import { Grid } from "@mui/material";
import { Container } from "@mui/system"; import { Container } from "@mui/system";
import styled from "styled-components"; import styled from "styled-components";




export const ChatGridLayoutContainer = styled(Container)` 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 Datei anzeigen

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) => { 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 = { 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 Datei anzeigen

position: relative; position: relative;
flex: 1; flex: 1;
height: 100%; height: 100%;
@media (max-width: 1024px) {
padding-right: 18px;
@media (max-width: 1200px) {
padding-right: 60px;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
padding-left: 18px; padding-left: 18px;

+ 4
- 3
src/pages/ChatMessages/ChatMessages.js Datei anzeigen

import React from "react"; 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 = () => { export const ChatMessagesPage = () => {
return ( return (
<ChatMessagesPageContainer>
</ChatMessagesPageContainer>
<ChatGridLayout content={<DirectChat /> } leftCard={<MiniChatColumn />} />
) )
} }



+ 0
- 1
src/pages/ChatMessages/ChatMessages.styled.js Datei anzeigen

max-width: none; max-width: none;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column;
`; `;

+ 4
- 2
src/pages/ItemDetailsPage/ItemDetailsPageMUI.js Datei anzeigen

import { useDispatch} from "react-redux"; import { useDispatch} from "react-redux";
import ItemDetails from "../../components/ItemDetails/ItemDetails"; import ItemDetails from "../../components/ItemDetails/ItemDetails";
import ItemDetailsLayout from "../../layouts/ItemDetailsLayout/ItemDetailsLayout"; import ItemDetailsLayout from "../../layouts/ItemDetailsLayout/ItemDetailsLayout";
import UserReviewsCard from "../../components/UserReviewsCard/UserReviewsCard";
import { fetchOneOffer } from "../../store/actions/offers/offersActions"; import { fetchOneOffer } from "../../store/actions/offers/offersActions";
import UserReviews from "../../components/UserReviews/UserReviews";


const ItemDetailsPage = (props) => { const ItemDetailsPage = (props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
} }
}, [offerId]); }, [offerId]);


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

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


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

+ 23
- 0
src/pages/MyOffers/MyOffers.js Datei anzeigen

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 Datei anzeigen

import { Box } from "@mui/material";
import styled from "styled-components";

export const MyOffersContainer = styled(Box)`

`

+ 0
- 0
src/pages/ProfilePage/ProfilePage.js Datei anzeigen


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

Laden…
Abbrechen
Speichern