浏览代码

Added chat, review, and made application responsible

feature/code-cleanup-joca
Djordje Mitrovic 3 年前
父节点
当前提交
97f4fc5308
共有 100 个文件被更改,包括 3517 次插入813 次删除
  1. 9
    3
      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. 7
    5
      src/components/Cards/CreateOfferCard/CreateOffer.js
  11. 20
    9
      src/components/Cards/CreateOfferCard/CreateOffer.styled.js
  12. 115
    98
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js
  13. 43
    3
      src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js
  14. 65
    55
      src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js
  15. 39
    12
      src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js
  16. 11
    7
      src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.styled.js
  17. 11
    4
      src/components/Cards/FilterCard/FilterCard.js
  18. 13
    4
      src/components/Cards/FilterCard/FilterCard.styled.js
  19. 0
    1
      src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js
  20. 0
    1
      src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js
  21. 39
    26
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.js
  22. 7
    4
      src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js
  23. 28
    0
      src/components/Cards/LittleOfferCard/LittleOfferCard.js
  24. 72
    0
      src/components/Cards/LittleOfferCard/LittleOfferCard.styled.js
  25. 34
    0
      src/components/Cards/MessageCard/MessageCard.js
  26. 48
    0
      src/components/Cards/MessageCard/MessageCard.styled.js
  27. 46
    0
      src/components/Cards/MiniChatCard/MiniChatCard.js
  28. 48
    0
      src/components/Cards/MiniChatCard/MiniChatCard.styled.js
  29. 39
    15
      src/components/Cards/OfferCard/OfferCard.js
  30. 26
    15
      src/components/Cards/OfferCard/OfferCard.styled.js
  31. 0
    0
      src/components/Cards/UserReviewsCard/Mockupdata.js
  32. 132
    0
      src/components/Cards/UserReviewsCard/UserReviewsCard.js
  33. 130
    0
      src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js
  34. 38
    32
      src/components/ChatColumn/ChatColumn.js
  35. 55
    11
      src/components/ChatColumn/ChatColumn.styled.js
  36. 126
    0
      src/components/CreateReview/CreateReview.js
  37. 103
    0
      src/components/CreateReview/CreateReview.styled.js
  38. 134
    0
      src/components/CreateReview/FirstStep/FirstStepCreateReview.js
  39. 115
    0
      src/components/CreateReview/FirstStep/FirstStepCreateReview.styled.js
  40. 55
    0
      src/components/CreateReview/SecondStep/SecondStepCreateReview.js
  41. 22
    0
      src/components/CreateReview/SecondStep/SecondStepCreateReview.styled.js
  42. 19
    0
      src/components/CreateReview/ThirdStep/ThirdStepCreateReview.js
  43. 35
    0
      src/components/CreateReview/ThirdStep/ThirdStepCreateReview.styled.js
  44. 81
    0
      src/components/DirectChat/DirectChat.js
  45. 5
    0
      src/components/DirectChat/DirectChat.styled.js
  46. 62
    0
      src/components/DirectChat/DirectChatContent/DirectChatContent.js
  47. 38
    0
      src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js
  48. 45
    0
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js
  49. 75
    0
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js
  50. 79
    0
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.js
  51. 6
    0
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.styled.js
  52. 20
    0
      src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js
  53. 17
    0
      src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.styled.js
  54. 71
    0
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js
  55. 41
    0
      src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.styled.js
  56. 80
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.js
  57. 8
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js
  58. 20
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js
  59. 25
    0
      src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.styled.js
  60. 143
    0
      src/components/Header/Drawer/Drawer.js
  61. 156
    0
      src/components/Header/Drawer/Drawer.styled.js
  62. 38
    114
      src/components/Header/Header.js
  63. 11
    10
      src/components/Header/Header.styled.js
  64. 6
    0
      src/components/ImagePicker/ImagePicker.styled.js
  65. 9
    10
      src/components/ItemDetails/Header/Header.js
  66. 0
    1
      src/components/ItemDetails/ItemDetails.js
  67. 18
    1
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js
  68. 16
    9
      src/components/MarketPlace/Header/Header.js
  69. 18
    0
      src/components/MarketPlace/Header/Header.styled.js
  70. 7
    3
      src/components/MarketPlace/MarketPlace.js
  71. 184
    37
      src/components/MarketPlace/Offers/Offers.js
  72. 5
    0
      src/components/Popovers/HeaderPopover/HeaderPopover.styled.js
  73. 18
    11
      src/components/Popovers/MyMessages/MyMessages.js
  74. 5
    0
      src/components/Popovers/MyPosts/MyPosts.js
  75. 1
    1
      src/components/Popovers/MyProfile/MyProfile.js
  76. 21
    2
      src/components/Profile/ProfileOffers/ProfileOffers.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. 74
    0
      src/components/UserReviews/UserReviews.js
  83. 48
    2
      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. 20
    2
      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

+ 9
- 3
src/AppRoutes.js 查看文件

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

@@ -16,7 +17,8 @@ import {
FORGOT_PASSWORD_PAGE,
PROFILE_PAGE,
CHAT_MESSAGE_PAGE,
CHAT_PAGE
CHAT_PAGE,
MY_OFFERS_PAGE
} from './constants/pages';
import LoginPage from './pages/LoginPage/LoginPage';
import HomePage from './pages/HomePage/HomePageMUI';
@@ -33,6 +35,7 @@ import ItemDetailsPage from './pages/ItemDetailsPage/ItemDetailsPageMUI';
import ProfilePage from './pages/ProfilePage/ProfilePage';
import ChatMessagesPage from './pages/ChatMessages/ChatMessages';
import ChatPage from './pages/Chat/Chat';
import MyOffers from './pages/MyOffers/MyOffers';


const AppRoutes = () => {
@@ -45,14 +48,17 @@ const AppRoutes = () => {
<Route path={REGISTER_PAGE} component={Register} />
<Route path={ERROR_PAGE} component={ErrorPage} />
<Route path={FORGOT_PASSWORD_MAIL_SENT} component={MailSent} />
<Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} />
<Route path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage}/>
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage}/>
<Route path={CREATE_OFFER_PAGE} component={CreateOffer}/>
<Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} />
<Route path={PROFILE_PAGE} component={ProfilePage} />
<Route path={HOME_PAGE} component={HomePage} />
<Route path={HOME_PAGE} component={(props) => {
return <HomePage key={props.match.params.id} />;
}}/>
<PrivateRoute path={CHAT_MESSAGE_PAGE} component={ChatMessagesPage} />
<PrivateRoute path={CHAT_PAGE} component={ChatPage} />
<PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} />
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>

+ 6
- 0
src/assets/images/svg/logo-image.svg 查看文件

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

+ 3
- 0
src/assets/images/svg/message.svg 查看文件

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

+ 6
- 0
src/assets/images/svg/package.svg 查看文件

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

+ 12
- 0
src/assets/images/svg/phone.svg 查看文件

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

+ 3
- 2
src/components/Buttons/IconButton/IconButton.js 查看文件

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

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

+ 5
- 1
src/components/Buttons/PrimaryButton/PrimaryButton.js 查看文件

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

+ 67
- 70
src/components/Cards/ChatCard/ChatCard.js 查看文件

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

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

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

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

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

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

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

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

+ 55
- 19
src/components/Cards/ChatCard/ChatCard.styled.js 查看文件

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

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

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

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


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

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

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

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

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

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

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

+ 7
- 5
src/components/Cards/CreateOfferCard/CreateOffer.js 查看文件

@@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useFormik } from "formik";
import { useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import { NavLink, useHistory } from "react-router-dom";
import * as Yup from "yup";
import { useTranslation } from "react-i18next";
import { fetchLogin } from "../../../store/actions/login/loginActions";
@@ -41,9 +41,10 @@ import { ReactComponent as ArrowBack } from "../../../assets/images/svg/arrow-ba
import { ReactComponent as CloseButton } from "../../../assets/images/svg/close-modal.svg";
import BackdropComponent from "../../MUI/BackdropComponent";

const CreateOffer = ({ history, closeCreateOfferModal }) => {
const CreateOffer = ({ closeCreateOfferModal }) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const history = useHistory();
const [informations, setInformations] = useState({});
const [showPassword, setShowPassword] = useState(false);
const [currentStep, setCurrentStep] = useState(1);
@@ -51,7 +52,6 @@ const CreateOffer = ({ history, closeCreateOfferModal }) => {
const handleMouseDownPassword = () => setShowPassword(!showPassword);
const categories = useSelector((state) => state.categories.categories);


// When user refreshes page
// useEffect(() => {
// function redirectClient() {
@@ -68,12 +68,14 @@ const CreateOffer = ({ history, closeCreateOfferModal }) => {
);

const handleApiResponseSuccess = (status) => {
// if (history.location.pathname !== HOME_PAGE) {
history.push({
pathname: HOME_PAGE,
state: {
from: history.location.pathname,
refetch: true,
},
});
// }
};

const handleSubmit = (values) => {
@@ -126,7 +128,7 @@ const CreateOffer = ({ history, closeCreateOfferModal }) => {
};

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

const handleSubmitOffer = () => {

+ 20
- 9
src/components/Cards/CreateOfferCard/CreateOffer.styled.js 查看文件

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

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

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

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

+ 115
- 98
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.js 查看文件

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


const { t } = useTranslation();
const handleSubmit = (values) => {
props.handleNext(values);
@@ -55,110 +54,119 @@ const FirstPartCreateOffer = (props) => {
};

return (
<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
/>
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
{/* <Backdrop position="absolute" isLoading={isLoading} /> */}

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

<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("location", value.target.value.city);
}}
>
<Option value="default">{t("offer.choseLocation")}</Option>
{locations.map((loc) => {
return (
<Option key={loc._if} value={loc}>
{loc.city}
</Option>
);
})}
</SelectField>
<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.category")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("category", value.target.value.name);
}}
>
<Option value="default">{t("offer.choseCategory")}</Option>
{categories.map((cat, i) => {
return (
<Option
key={i}
value={cat}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</Option>
);
})}
</SelectField>
<FieldLabel leftText={t("offer.location")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("location", value.target.value.city);
}}
>
<SelectOption value="default">
{t("offer.choseLocation")}
</SelectOption>
{locations.map((loc) => {
return (
<SelectOption key={loc._if} value={loc}>
{loc.city}
</SelectOption>
);
})}
</SelectField>

<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value.name);
}}
>
<Option value="default">{t("offer.choseSubcategory")}</Option>
{subcat?.length > 0 &&
subcat[0]?.subcategories &&
subcat[0].subcategories.map((sub, i) => {
<FieldLabel leftText={t("offer.category")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("category", value.target.value.name);
}}
>
<SelectOption value="default">
{t("offer.choseCategory")}
</SelectOption>
{categories.map((cat, i) => {
return (
<Option key={i} value={sub}>
{sub.name}
</Option>
<SelectOption
key={i}
value={cat}
onClick={() => handleSubcategories(cat.name)}
>
{cat.name}
</SelectOption>
);
})}
</SelectField>
</SelectField>

<FieldLabel leftText={t("offer.subcategory")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("subcategory", value.target.value.name);
}}
>
<SelectOption value="default">
{t("offer.choseSubcategory")}
</SelectOption>
{subcat?.length > 0 &&
subcat[0]?.subcategories &&
subcat[0].subcategories.map((sub, i) => {
return (
<SelectOption key={i} value={sub}>
{sub.name}
</SelectOption>
);
})}
</SelectField>
</CreateOfferFormContainer>
<NextButton
type="submit"
variant="contained"
@@ -166,14 +174,23 @@ const FirstPartCreateOffer = (props) => {
fullWidth
buttoncolor={selectedTheme.primaryPurple}
textcolor="white"
// disabled={
// formik.values.username.length === 0 ||
// formik.values.password.length === 0
// }
onClick={formik.handleSubmit}
disabled={
formik.values?.nameOfProduct?.length === 0 ||
!formik.values?.nameOfProduct ||
formik.values?.description?.length === 0 ||
!formik.values?.description ||
formik.values?.category?.length === 0 ||
!formik.values?.category ||
formik.values?.subcategory?.length === 0 ||
!formik.values?.subcategory ||
formik.values?.location?.length === 0 ||
!formik.values?.location
}
>
{t("offer.continue")}
</NextButton>
</CreateOfferFormContainer>
</>
);
};


+ 43
- 3
src/components/Cards/CreateOfferCard/FirstPart/FirstPartCreateOffer.styled.js 查看文件

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

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

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

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

+ 65
- 55
src/components/Cards/CreateOfferCard/SecondPart/SecondPartCreateOffer.js 查看文件

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

let imagesEmpty = 0;
images.forEach(item => {
if (item === null || item === undefined) imagesEmpty++;
})
const imagesEmpty = useMemo(() => {
let numOfImagesEmpty = 0;
images.forEach((item) => {
if (item === null || item === undefined) numOfImagesEmpty++;
});
return numOfImagesEmpty;
}, [images]);
// for (let i = 0; i < numberOfImages; i++) {
// let item = images[i];
// if (item === null || item === undefined) imagesEmpty++;
// }

const handleSubmit = (values) => {
props.handleNext(values);
@@ -53,54 +59,58 @@ const SecondPartCreateOffer = (props) => {
});

return (
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
<Scroller>
{images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))}
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
</SupportedFormats>
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue="default"
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}
>
{t("offer.continue")}
</NextButton>
</InputButtonContainer>
</CreateOfferFormContainer>
<>
<CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}>
<Scroller>
{images.map((item, index) => (
<ImagePicker
key={index}
image={item}
setImage={(image) => setImage(index, image)}
deleteImage={() => setImage(index, null)}
showDeleteIcon
/>
))}
</Scroller>
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
</SupportedFormats>
<InputButtonContainer>
<FieldLabel leftText={t("offer.condition")} />
<SelectField
defaultValue="default"
onChange={(value) => {
formik.setFieldValue("condition", value.target.value);
}}
>
<SelectOption style={{display: "none"}} 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}
onClick={formik.handleSubmit}
textcolor="white"
disabled={
imagesEmpty === numberOfImages || formik.values.condition.length === 0
}
>
{t("offer.continue")}
</NextButton>
</>
);
};


+ 39
- 12
src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.js 查看文件

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

const ThirdPartCreateOffer = (props) => {
const {t} = useTranslation();
const offer = {
offer: {
category: {
@@ -11,24 +19,43 @@ const ThirdPartCreateOffer = (props) => {
subcategory: props.informations.subcategory,
condition: props.informations.condition,
_created: new Date().toString(),
images: props.informations.images.filter(item => item !== undefined),
images: props.informations.images.filter((item) => item !== undefined),
name: props.informations.nameOfProduct,
description: props.informations.description
}
}
description: props.informations.description,
},
};
const handleSubmit = (e) => {
e.preventDefault();
props.handleSubmitOffer();
};

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
- 7
src/components/Cards/CreateOfferCard/ThirdPart/ThirdPartCreateOffer.styled.js 查看文件

@@ -1,12 +1,16 @@
import { Box } from "@mui/material";
// import { Box } from "@mui/material";
import styled from "styled-components";
import ItemDetailsCard from "../../ItemDetailsCard/ItemDetailsCard";

export const CreateOfferFormContainer = styled(Box)`
padding-top: 20px;
margin-top: 20px;
width:100%;
`;
// export const CreateOfferFormContainer = styled(Box)`
// padding-top: 20px;
// margin-top: 20px;
// width:100%;
// `;
export const PreviewCard = styled(ItemDetailsCard)`

position: relative;
top: 10px;
width: 100%;
overflow-y: auto;
height: 400px;
`

+ 11
- 4
src/components/Cards/FilterCard/FilterCard.js 查看文件

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

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

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

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

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

export default FilterCard;

+ 13
- 4
src/components/Cards/FilterCard/FilterCard.styled.js 查看文件

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

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

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

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

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Checkbox/FilterCheckboxDropdown.js 查看文件

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

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

+ 0
- 1
src/components/Cards/FilterCard/FilterDropdown/Radio/FilterRadioDropdown.js 查看文件

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

console.log(props.selected)

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

+ 39
- 26
src/components/Cards/ItemDetailsCard/ItemDetailsCard.js 查看文件

@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import {
CheckButton,
@@ -16,18 +16,33 @@ import {
OfferDetails,
OfferImage,
Scroller,
PublishButtonContainer,
} from "./ItemDetailsCard.styled";
import { NextButton } from "../CreateOfferCard/FirstPart/FirstPartCreateOffer.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";
import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import selectedTheme from "../../../themes";
import { useDispatch, useSelector } from "react-redux";
import { selectLatestChats } from "../../../store/selectors/chatSelectors";
import { useHistory } from "react-router-dom";
import { increaseCounter } from "../../../store/actions/counter/counterActions";
import _ from "lodash";

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

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

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

export default ItemDetailsCard;

+ 7
- 4
src/components/Cards/ItemDetailsCard/ItemDetailsCard.styled.js 查看文件

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

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

+ 28
- 0
src/components/Cards/LittleOfferCard/LittleOfferCard.js 查看文件

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

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

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

export default LittleOfferCard

+ 72
- 0
src/components/Cards/LittleOfferCard/LittleOfferCard.styled.js 查看文件

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

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

+ 34
- 0
src/components/Cards/MessageCard/MessageCard.js 查看文件

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

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

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

export default MessageCard;

+ 48
- 0
src/components/Cards/MessageCard/MessageCard.styled.js 查看文件

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

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

+ 46
- 0
src/components/Cards/MiniChatCard/MiniChatCard.js 查看文件

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

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

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

export default MiniChatCard;

+ 48
- 0
src/components/Cards/MiniChatCard/MiniChatCard.styled.js 查看文件

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

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

+ 39
- 15
src/components/Cards/OfferCard/OfferCard.js 查看文件

@@ -27,6 +27,8 @@ import {
OfferViews,
RemoveIcon,
RemoveIconContainer,
StarIcon,
StarIconContainer,
} from "./OfferCard.styled";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
@@ -39,14 +41,22 @@ const OfferCard = (props) => {
const routeToItem = (itemId) => {
history.push(`/proizvodi/${itemId}`);
};
const messageUser = () => {
props.messageUser(props.offer);
};
const makeReview = () => {
if (!props.disabledReviews) {
props.makeReview(props.offer);
}
};
return (
<React.Fragment>
<OfferCardContainer
vertical={props.vertical}
sponsored={
props.pinned !== undefined
? props.pinned.toString()
: props?.offer?.pinned.toString()
props?.pinned !== undefined
? props?.pinned?.toString()
: props?.offer?.pinned?.toString()
}
halfwidth={props.halfwidth ? 1 : 0}
>
@@ -59,7 +69,7 @@ const OfferCard = (props) => {
<OfferFlexContainer vertical={props.vertical}>
<OfferImageContainer vertical={props.vertical}>
<OfferImage
src={props?.offer?.images[0]}
src={props?.offer?.images ? props?.offer?.images[0] : ""}
vertical={props.vertical}
></OfferImage>
</OfferImageContainer>
@@ -83,14 +93,14 @@ const OfferCard = (props) => {
<DetailIcon color="black" component="span" size="16px">
<Category width={"14px"} />
</DetailIcon>
<DetailText>{props?.offer?.category.name}</DetailText>
<DetailText>{props?.offer?.category?.name}</DetailText>
</OfferCategory>
<OfferViews vertical={props.vertical}>
{props.dontShowViews ? (<></>) : (<OfferViews vertical={props.vertical}>
<DetailIcon color="black" component="span" size="16px">
<EyeIcon />
</DetailIcon>
<DetailText>{props?.offer?.views?.viewers?.length}</DetailText>
</OfferViews>
<DetailText>{props?.offer?.views?.count}</DetailText>
</OfferViews>)}
</OfferDetails>
</OfferInfo>
{!props.halfwidth ? (
@@ -121,15 +131,22 @@ const OfferCard = (props) => {

{props.isMyOffer ? (
<>
<RemoveIconContainer vertical={props.vertical}>
<RemoveIcon />
</RemoveIconContainer>
<EditIconContainer vertical={props.vertical}>
<EditIcon />
</EditIconContainer>
<RemoveIconContainer vertical={props.vertical}>
<RemoveIcon />
</RemoveIconContainer>
<EditIconContainer vertical={props.vertical}>
<EditIcon />
</EditIconContainer>
</>
) : props.aboveChat ? (
<StarIconContainer
disabled={props.disabledReviews}
onClick={makeReview}
>
<StarIcon disabled={props.disabledReviews} />
</StarIconContainer>
) : (
<MessageIcon vertical={props.vertical}>
<MessageIcon vertical={props.vertical} onClick={messageUser}>
<Message />
</MessageIcon>
)}
@@ -157,10 +174,17 @@ OfferCard.propTypes = {
pinned: PropTypes.bool,
vertical: PropTypes.bool,
isMyOffer: PropTypes.bool,
aboveChat: PropTypes.bool,
disabledReviews: PropTypes.bool,
messageUser: PropTypes.func,
makeReview: PropTypes.func,
dontShowViews: PropTypes.bool,
};
OfferCard.defaultProps = {
halfwidth: false,
sponsored: false,
messageUser: () => {},
makeReview: () => {},
};

export default OfferCard;

+ 26
- 15
src/components/Cards/OfferCard/OfferCard.styled.js 查看文件

@@ -7,8 +7,7 @@ import { Icon } from "../../Icon/Icon";
import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg";
import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg";
import { ReactComponent as Edit } from "../../../assets/images/svg/edit.svg";


import { ReactComponent as Star } from "../../../assets/images/svg/star.svg";

export const OfferCardContainer = styled(Container)`
display: flex;
@@ -290,13 +289,14 @@ export const OfferImageContainer = styled(Box)`
height: 144px;
@media (max-width: 600px) {
${(props) =>
!props.vertical ?
`
!props.vertical
? `
min-width: 108px;
min-height: 108px;
width: 108px;
height: 108px;
` : `margin-top: 4px;`}
`
: `margin-top: 4px;`}
border-radius: 4px;
overflow: hidden;
box-shadow: 4px 4px 9px rgba(0, 0, 0, 0.12);
@@ -320,15 +320,26 @@ export const EyeIcon = styled(Eye)`
top: 1px !important;
}
`;
export const RemoveIconContainer = styled(MessageIcon)`

`
export const RemoveIcon = styled(Remove)`

`
export const RemoveIconContainer = styled(MessageIcon)``;
export const RemoveIcon = styled(Remove)``;
export const EditIconContainer = styled(MessageIcon)`
right: 70px;
`
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 查看文件


+ 132
- 0
src/components/Cards/UserReviewsCard/UserReviewsCard.js 查看文件

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

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

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

// const dispatch = useDispatch();

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

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

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

return (
<ReviewContainer key={review?.image}>
<ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}>
<ProfileImageContainer>
<ProfileImage alt={review?.name} src={review?.image} />
</ProfileImageContainer>
<ProfileName sx={{ color: selectedTheme.primaryPurple }}>
<b>{review?.name}</b>
</ProfileName>
</ListItem>
<ReviewQuote
container
direction="row"
justifyContent="start"
alignItems="center"
spacing={2}
sx={{ pl: 2, py: 2 }}
>
<ThumbBox item xs={1}>
{isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />}
</ThumbBox>
<ReviewQuoteBox item xs={11}>
<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;

+ 130
- 0
src/components/Cards/UserReviewsCard/UserReviewsCard.styled.js 查看文件

@@ -0,0 +1,130 @@
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%;
height: calc(100% - 90px);
max-height: 100vh;
@media (max-width: 1200px) {
padding: 0;
}
@media (max-width: 600px) {
position: relative;
top: -45px;
overflow: hidden;
height: 350px;
max-height: 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;
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)`
`

+ 38
- 32
src/components/ChatColumn/ChatColumn.js 查看文件

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

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

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

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

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

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

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

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

+ 55
- 11
src/components/ChatColumn/ChatColumn.styled.js 查看文件

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

export const ChatColumnContainer = styled(Container)`

`;
margin-bottom: 40px;
`;

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

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

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

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

+ 126
- 0
src/components/CreateReview/CreateReview.js 查看文件

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

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

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

export default CreateReview;

+ 103
- 0
src/components/CreateReview/CreateReview.styled.js 查看文件

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

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

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

top: 36px;
left: 36px;

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

+ 134
- 0
src/components/CreateReview/FirstStep/FirstStepCreateReview.js 查看文件

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

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

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

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

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

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

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

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

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

export default FirstStepCreateReview;

+ 115
- 0
src/components/CreateReview/FirstStep/FirstStepCreateReview.styled.js 查看文件

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

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

+ 55
- 0
src/components/CreateReview/SecondStep/SecondStepCreateReview.js 查看文件

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

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

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

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

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

export default SecondStepCreateReview;

+ 22
- 0
src/components/CreateReview/SecondStep/SecondStepCreateReview.styled.js 查看文件

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

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

+ 19
- 0
src/components/CreateReview/ThirdStep/ThirdStepCreateReview.js 查看文件

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

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

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

export default ThirdStepCreateReview

+ 35
- 0
src/components/CreateReview/ThirdStep/ThirdStepCreateReview.styled.js 查看文件

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

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

+ 81
- 0
src/components/DirectChat/DirectChat.js 查看文件

@@ -0,0 +1,81 @@
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(() => {
refreshChat();
}, [routeMatch.params.idChat]);
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 查看文件

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

export const DirectChatContainer = styled(Box)`
`

+ 62
- 0
src/components/DirectChat/DirectChatContent/DirectChatContent.js 查看文件

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

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

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

export default DirectChatContent;

+ 38
- 0
src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js 查看文件

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

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

+ 45
- 0
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js 查看文件

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

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

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

export default DirectChatContentHeader;

+ 75
- 0
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js 查看文件

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


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

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

+ 79
- 0
src/components/DirectChat/DirectChatHeader/DirectChatHeader.js 查看文件

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

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

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

export default DirectChatHeader;

+ 6
- 0
src/components/DirectChat/DirectChatHeader/DirectChatHeader.styled.js 查看文件

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

export const DirectChatHeaderContainer = styled(Box)`

`

+ 20
- 0
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.js 查看文件

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

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

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

export default DirectChatHeaderTitle

+ 17
- 0
src/components/DirectChat/DirectChatHeaderTitle/DirectChatHeaderTitle.styled.js 查看文件

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

export const DirectChatHeaderTitleContainer = styled(Box)`

`
export const MessageIcon = styled(Message)`

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

+ 71
- 0
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.js 查看文件

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

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

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

export default DirectChatNewMessage;

+ 41
- 0
src/components/DirectChat/DirectChatNewMessage/DirectChatNewMessage.styled.js 查看文件

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

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

+ 80
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumn.js 查看文件

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

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

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

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

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

export default MiniChatColumn;

+ 8
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js 查看文件

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

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

+ 20
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.js 查看文件

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

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

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

export default MiniChatColumnHeader

+ 25
- 0
src/components/DirectChat/MiniChatColumn/MiniChatColumnHeader/MiniChatColumnHeaderTitle.styled.js 查看文件

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


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

+ 143
- 0
src/components/Header/Drawer/Drawer.js 查看文件

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

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

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

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

+ 156
- 0
src/components/Header/Drawer/Drawer.styled.js 查看文件

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


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

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


+ 38
- 114
src/components/Header/Header.js 查看文件

@@ -1,9 +1,7 @@
import React, { useState, useMemo, useEffect, useRef } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
AddOfferButton,
AuthButtonsContainer,
AuthButtonsDrawerContainer,
DrawerContainer,
EndIcon,
FilterContainer,
FilterIcon,
@@ -21,20 +19,13 @@ import {
UserName,
} from "./Header.styled";
import PropTypes from "prop-types";
import {
AppBar,
Badge,
Toolbar,
useMediaQuery,
Typography,
} from "@mui/material";
import { AppBar, Badge, Toolbar, useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/system";
import MenuOutlinedIcon from "@mui/icons-material/MenuOutlined";
import MailIcon from "@mui/icons-material/EmailOutlined";
import Autorenew from "@mui/icons-material/Autorenew";
import AccountCircle from "@mui/icons-material/PersonOutlineOutlined";
import Drawer from "../MUI/DrawerComponent";
import { PrimaryButton } from "../Buttons/PrimaryButton/PrimaryButton";
import PopoverComponent from "../Popovers/PopoverComponent";
import { MyPosts } from "../Popovers/MyPosts/MyPosts";
import { MyMessages } from "../Popovers/MyMessages/MyMessages";
@@ -48,16 +39,24 @@ import { selectUserId } from "../../store/selectors/loginSelectors";
import { useSearch } from "../../hooks/useSearch";
import { selectProfileName } from "../../store/selectors/profileSelectors";
import { useHistory, useRouteMatch } from "react-router-dom";
import { HOME_PAGE, LOGIN_PAGE, REGISTER_PAGE } from "../../constants/pages";
import {
FORGOT_PASSWORD_MAIL_SENT,
FORGOT_PASSWORD_PAGE,
HOME_PAGE,
LOGIN_PAGE,
REGISTER_PAGE,
REGISTER_SUCCESSFUL_PAGE,
RESET_PASSWORD_PAGE,
} from "../../constants/pages";
import useFilters from "../../hooks/useFilters";
import FilterCard from "../Cards/FilterCard/FilterCard";
import { useQueryString } from "../../hooks/useQueryString";
import { convertQueryStringFrontend } from "../../util/helpers/queryHelpers";
import { fetchMineProfile } from "../../store/actions/profile/profileActions";
import CreateOffer from "../Cards/CreateOfferCard/CreateOffer";
import { Drawer as HeaderDrawer } from "./Drawer/Drawer";

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

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

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

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

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

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

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

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

+ 11
- 10
src/components/Header/Header.styled.js 查看文件

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

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

+ 6
- 0
src/components/ImagePicker/ImagePicker.styled.js 查看文件

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

+ 9
- 10
src/components/ItemDetails/Header/Header.js 查看文件

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

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

const Header = () => {

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

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

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

+ 0
- 1
src/components/ItemDetails/ItemDetails.js 查看文件

@@ -19,7 +19,6 @@ const ItemDetails = () => {
}
return false;
}, [offer, userId])
console.log(isMyProfile)
return (
<ItemDetailsContainer>
<Header/>

+ 18
- 1
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js 查看文件

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

const ItemDetailsHeaderCard = (props) => {
const history = useHistory();
const chats = useSelector(selectLatestChats);
const offer = props.offer;
const userId = useSelector(selectUserId);
if (!props.offer) {
return <div>Loading...</div>;
}
@@ -42,6 +47,18 @@ const ItemDetailsHeaderCard = (props) => {
const handleGoProfile = () => {
history.push(`/profile/${offer?.offer?.userId}`);
};
const messageUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?.offer?._id);
if (chatItem !== undefined) {
history.push(`/messages/${chatItem.chat._id}`)
} else {
if (offer?.offer?.userId !== userId) {
history.push(`/messages/newMessage`, {
offerId: offer?.offer?._id
})
}
}
}
return (
<ItemDetailsHeaderContainer
isMyProfile={props.isMyProfile}
@@ -79,7 +96,7 @@ const ItemDetailsHeaderCard = (props) => {
<UserIcon />
</UserIconContainer>
) : (
<MessageIcon>
<MessageIcon onClick={() => messageUser(offer)}>
<MessageColor />
</MessageIcon>
)}

+ 16
- 9
src/components/MarketPlace/Header/Header.js 查看文件

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

+ 18
- 0
src/components/MarketPlace/Header/Header.styled.js 查看文件

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

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

+ 7
- 3
src/components/MarketPlace/MarketPlace.js 查看文件

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

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

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

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

export default MarketPlace;

+ 184
- 37
src/components/MarketPlace/Offers/Offers.js 查看文件

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

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

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

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

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

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

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

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

const allOffersToShow = useMemo(() => {
let newOffers = [...pinnedOffersToShow, ...offersToShow];
if (props.myOffers) {
if (selectedCategory && selectedCategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.category.name === selectedCategory.name
);
}
if (selectedSubcategory && selectedSubcategory?._id !== 0) {
newOffers = newOffers.filter(
(item) => item.subcategory === selectedSubcategory.name
);
}
if (selectedLocations && selectedLocations?.length > 0) {
newOffers = newOffers.filter((item) => {
let isInOneOfLocations = false;
selectedLocations?.forEach((location) => {
if (item.location.city === location.city) {
isInOneOfLocations = true;
}
});
return isInOneOfLocations;
});
}
let oldOffers = [...offersToShow];
let oldPinnedOffers = [...pinnedOffersToShow];
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.NEW.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemB._created) - new Date(itemA._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.OLD.value
) {
newOffers = newOffers.sort(
(itemA, itemB) => new Date(itemA._created) - new Date(itemB._created)
);
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
...oldOffers.sort(
(itemA, itemB) =>
new Date(itemA._created) - new Date(itemB._created)
),
];
}
if (
selectedSortOption &&
selectedSortOption.value === sortEnum.POPULAR.value
) {
newOffers = [
...oldPinnedOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
...oldOffers.sort(
(itemA, itemB) => itemB.views.count - itemA.views.count
),
];
}
newOffers = newOffers.slice((page - 1) * 10, page * 10);
}
return newOffers;
}, [pinnedOffersToShow, offersToShow, props.myOffers, page]);

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

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

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

const messageOneUser = (offer) => {
const chatItem = chats.find(item => item.chat.offerId === offer?.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 (
<OffersContainer ref={offersRef}>
<>
{pinnedOffers != undefined &&
pinnedOffers.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />
);
})}
</>
{offers != undefined &&
offers.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} />
);
})}
{allOffersToShow.map((item) => {
return (
<OfferCard key={item._id} offer={item} halfwidth={props.isGrid} messageUser={messageOneUser} />
);
})}
{allOffersToShow?.length === 0 && (
<>akjshdkjhadsjkasjhkd</>
)}
<Paging
totalElements={total}
totalElements={totalOffers}
elementsPerPage={10}
current={page}
changePage={handleDifferentPage}
@@ -108,6 +250,11 @@ const Offers = (props) => {
Offers.propTypes = {
children: PropTypes.node,
isGrid: PropTypes.bool,
myOffers: PropTypes.bool,
};

Offers.defaultProps = {
myOffers: false,
};

export default Offers;

+ 5
- 0
src/components/Popovers/HeaderPopover/HeaderPopover.styled.js 查看文件

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

+ 18
- 11
src/components/Popovers/MyMessages/MyMessages.js 查看文件

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

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

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

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

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

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

+ 5
- 0
src/components/Popovers/MyPosts/MyPosts.js 查看文件

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

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

+ 1
- 1
src/components/Popovers/MyProfile/MyProfile.js 查看文件

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

+ 21
- 2
src/components/Profile/ProfileOffers/ProfileOffers.js 查看文件

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

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

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

+ 1
- 1
src/components/Router/PrivateRoute.js 查看文件

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

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

+ 27
- 0
src/components/UserReviews/NoReviews/NoReviews.js 查看文件

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

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

export default NoReviews;

+ 23
- 0
src/components/UserReviews/NoReviews/NoReviews.styled.js 查看文件

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

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

src/components/UserReviewsCard/UserReviewsSkeleton/UserReviewsSkeleton.js → src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.js 查看文件


src/components/UserReviewsCard/UserReviewsSkeleton/UserReviewsSkeleton.styled.js → src/components/UserReviews/NoReviews/UserReviewsSkeleton/UserReviewsSkeleton.styled.js 查看文件

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

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

+ 74
- 0
src/components/UserReviews/UserReviews.js 查看文件

@@ -0,0 +1,74 @@
import React, { 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 { useSelector } from "react-redux";
import { selectOffer } from "../../store/selectors/offersSelectors";

const UserReviews = (props) => {
const { t } = useTranslation();
const offer = useSelector(selectOffer);
const lastThreeReviews = useMemo(() => {
if (props.givingReview) {
return [...props.profileReviews];
}
if (offer.companyData?.lastThreeReviews) {
return [...offer.companyData.lastThreeReviews]
}
return []
}, [props.profileReviews, offer]);

return (
<ReviewsBox className={props.className}>
{!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;

src/components/UserReviewsCard/UserReviewsCard.styled.js → src/components/UserReviews/UserReviews.styled.js 查看文件

@@ -7,14 +7,14 @@ import selectedTheme from "../../themes";
export const ReviewsBox = styled(Box)`
width: 100%;
height: calc(100% - 90px);
max-height: 100vh;
@media (max-width: 1200px) {
padding: 0 50px;
padding: 0;
}
@media (max-width: 600px) {
position: relative;
top: -45px;
overflow: hidden;
height: 350px;
max-height: 350px;
padding: 0;
}
@@ -37,6 +37,7 @@ 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};
@@ -82,3 +83,48 @@ export const NoReviewsAltText = styled(Typography)`
width: 100%;
margin-bottom: 36px;
`
export const ProfileImage = styled.img`
width: 54px;
height: 54px;
border-radius: 100%;
`
export const ProfileImageContainer = styled(Box)`
width: 54px;
height: 54px;
border-radius: 100%;
margin-right: 14px;
`
export const ReviewQuote = styled(Grid)`
position: relative;
left: 8px;
`
export const ThumbBox = styled(Grid)`
`
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 查看文件

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

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

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

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

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

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

export default UserReviewsCard;

+ 2
- 1
src/constants/pages.js 查看文件

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

+ 14
- 0
src/enums/reviewEnum.js 查看文件

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

+ 13
- 1
src/hooks/useFilters.js 查看文件

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



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

+ 6
- 7
src/hooks/useQueryString.js 查看文件

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

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

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

+ 14
- 11
src/hooks/useSorting.js 查看文件

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

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

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

+ 20
- 2
src/i18n/resources/rs.js 查看文件

@@ -22,6 +22,7 @@ export default {
labelPhone: "Telefon",
labelLocation: "Lokacija",
labelWebsite: "Adresa Websajta",
logout: "Odjavi se",
next: "Sledeće",
nextPage: "Sledeća strana",
previousPage: "Prethodna strana",
@@ -157,6 +158,7 @@ export default {
supportedImagesFormats:
"Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>",
continue: "NASTAVI",
publish: "OBJAVI",
},
apiErrors: {
somethingWentWrong: "Greska sa serverom!",
@@ -170,9 +172,25 @@ export default {
checkEverything: "POGLEDAJ SVE",
myMessages: "Moje poruke",
newOffers: "Najnovije ponude",
navMenu: "Navigacioni Meni"
},
reviews: {
title: "Ova kompanija još uvek nema ocenu.",
altTitle: "Budite prvi da je ocenite"
}
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...",
},
};

+ 26
- 21
src/layouts/ChatGridLayout/ChatGridLayout.js 查看文件

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

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

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

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

export default ChatGridLayout
export default ChatGridLayout;

+ 16
- 1
src/layouts/ChatGridLayout/ChatGridLayout.styled.js 查看文件

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


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

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

+ 23
- 18
src/layouts/ChatLayout/ChatLayout.js 查看文件

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

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

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

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

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

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

export default ChatLayout;
export default ChatLayout;

+ 2
- 2
src/layouts/ItemDetailsLayout/ItemDetailsLayout.styled.js 查看文件

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

+ 4
- 3
src/pages/ChatMessages/ChatMessages.js 查看文件

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


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


+ 0
- 1
src/pages/ChatMessages/ChatMessages.styled.js 查看文件

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

+ 4
- 2
src/pages/ItemDetailsPage/ItemDetailsPageMUI.js 查看文件

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

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

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

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

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

+ 23
- 0
src/pages/MyOffers/MyOffers.js 查看文件

@@ -0,0 +1,23 @@
import React from "react";
import PropTypes from "prop-types";
import { MyOffersContainer } from "./MyOffers.styled";
import MainLayout from "../../layouts/MainLayout/MainLayout";
import FilterCard from "../../components/Cards/FilterCard/FilterCard";
import MarketPlace from "../../components/MarketPlace/MarketPlace";

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

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

export default MyOffers;

+ 6
- 0
src/pages/MyOffers/MyOffers.styled.js 查看文件

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

export const MyOffersContainer = styled(Box)`

`

+ 0
- 0
src/pages/ProfilePage/ProfilePage.js 查看文件


部分文件因为文件数量过多而无法显示

正在加载...
取消
保存