| @@ -1,3 +1,4 @@ | |||
| /* eslint-disable */ | |||
| import React from "react"; | |||
| import { Redirect, Route, Switch } from "react-router-dom"; | |||
| @@ -17,6 +18,7 @@ import { | |||
| PROFILE_PAGE, | |||
| CHAT_MESSAGE_PAGE, | |||
| CHAT_PAGE, | |||
| MY_OFFERS_PAGE, | |||
| } from "./constants/pages"; | |||
| import LoginPage from "./pages/LoginPage/LoginPage"; | |||
| import HomePage from "./pages/HomePage/HomePageMUI"; | |||
| @@ -33,6 +35,7 @@ import ItemDetailsPage from "./pages/ItemDetailsPage/ItemDetailsPageMUI"; | |||
| import ProfilePage from "./pages/ProfilePage/ProfilePage"; | |||
| import ChatMessagesPage from "./pages/ChatMessages/ChatMessages"; | |||
| import ChatPage from "./pages/Chat/Chat"; | |||
| import MyOffers from "./pages/MyOffers/MyOffers"; | |||
| const AppRoutes = () => { | |||
| return ( | |||
| @@ -49,9 +52,15 @@ const AppRoutes = () => { | |||
| <Route path={CREATE_OFFER_PAGE} component={CreateOffer} /> | |||
| <Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} /> | |||
| <Route path={PROFILE_PAGE} component={ProfilePage} /> | |||
| <Route path={HOME_PAGE} component={HomePage} /> | |||
| <Route | |||
| path={HOME_PAGE} | |||
| component={(props) => { | |||
| return <HomePage key={props.match.params.id} />; | |||
| }} | |||
| /> | |||
| <PrivateRoute path={CHAT_MESSAGE_PAGE} component={ChatMessagesPage} /> | |||
| <PrivateRoute path={CHAT_PAGE} component={ChatPage} /> | |||
| <PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} /> | |||
| <Redirect from="*" to={NOT_FOUND_PAGE} /> | |||
| </Switch> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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, | |||
| } | |||
| @@ -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> | |||
| @@ -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, | |||
| @@ -5,6 +5,7 @@ import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Icon } from "../../Icon/Icon"; | |||
| import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as Phone } from "../../../assets/images/svg/phone.svg"; | |||
| export const ChatCardContainer = styled(Container)` | |||
| display: flex; | |||
| @@ -27,7 +28,8 @@ export const ChatCardContainer = styled(Container)` | |||
| position: relative; | |||
| justify-content: space-between; | |||
| @media (max-width: 550px) { | |||
| height: auto; | |||
| max-height: 108px; | |||
| margin: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| @@ -42,8 +44,8 @@ export const UserImage = styled.img` | |||
| width: 144px; | |||
| height: 144px; | |||
| @media (max-width: 600px) { | |||
| width: 90px; | |||
| height: 90px; | |||
| width: 72px; | |||
| height: 72px; | |||
| } | |||
| `; | |||
| @@ -53,8 +55,9 @@ export const UserImgWrapper = styled(Box)` | |||
| width: 144px; | |||
| height: 144px; | |||
| @media (max-width: 600px) { | |||
| width: 90px; | |||
| height: 90px; | |||
| width: 72px; | |||
| height: 72px; | |||
| min-width: 80px; | |||
| } | |||
| `; | |||
| export const OfferImgWrapper = styled(Box)` | |||
| @@ -62,10 +65,10 @@ export const OfferImgWrapper = styled(Box)` | |||
| border-radius: 4px; | |||
| width: 72px; | |||
| height: 72px; | |||
| min-width: 72px; | |||
| max-width: 72px; | |||
| `; | |||
| export const OfferFlexContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| @@ -89,12 +92,11 @@ export const OfferCardContainer = styled(Container)` | |||
| max-width: 2000px; | |||
| position: relative; | |||
| @media (max-width: 550px) { | |||
| } | |||
| `; | |||
| export const OfferCardContainerMobile = styled(Box)` | |||
| display: none; | |||
| display: none; | |||
| @media (max-width: 550px) { | |||
| position: relative; | |||
| @@ -146,7 +148,7 @@ export const OfferTitleMobile = styled(Typography)` | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 18px; | |||
| font-size: 12px; | |||
| cursor: pointer; | |||
| @media (max-width: 550px) { | |||
| display: block; | |||
| @@ -290,16 +292,26 @@ export const DetailText = styled(Typography)` | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 180px; | |||
| height: 48px; | |||
| background-color: ${selectedTheme.primaryPurple}; | |||
| &:hover button { | |||
| background-color: ${selectedTheme.primaryPurple} !important; | |||
| &:hover { | |||
| background-color: ${selectedTheme.primaryPurple}; | |||
| color: white !important; | |||
| border-radius: 4px; | |||
| } | |||
| @media (max-width: 1024px) { | |||
| width: 150px; | |||
| height: 40px; | |||
| margin-left: 2vw; | |||
| & button { | |||
| padding: 0; | |||
| font-size: 11px; | |||
| } | |||
| } | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| transition: 0.2s all; | |||
| `; | |||
| export const MessageIcon = styled(IconButton)` | |||
| export const PhoneIconContainer = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| @@ -307,8 +319,8 @@ export const MessageIcon = styled(IconButton)` | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| @media (max-width: 600px) { | |||
| width: 30px; | |||
| height: 30px; | |||
| width: 32px; | |||
| height: 32px; | |||
| top: 16px; | |||
| right: 16px; | |||
| padding: 0; | |||
| @@ -369,6 +381,7 @@ export const ChatOffer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| padding-left: 36px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| @@ -382,7 +395,7 @@ export const OfferText = styled(Box)` | |||
| export const OfferTextMobile = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: "12px"; | |||
| font-size: 9px; | |||
| color: ${selectedTheme.primaryText}; | |||
| `; | |||
| @@ -410,6 +423,13 @@ export const Col = styled(Box)` | |||
| align-items: center; | |||
| flex-direction: row; | |||
| gap: 18px; | |||
| flex: 1; | |||
| @media (max-width: 1024px) { | |||
| ${(props) => props.mobileDisappear && `display: none;`} | |||
| } | |||
| @media (max-width: 600px) { | |||
| ${(props) => props.mobileDisappear && `display: none;`} | |||
| } | |||
| `; | |||
| export const UserName = styled(Typography)` | |||
| @@ -426,9 +446,16 @@ export const UserName = styled(Typography)` | |||
| export const LastMessage = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| color: ${selectedTheme.primaryDarkTextThird}; | |||
| line-height: 22px; | |||
| font-size: 16px; | |||
| max-width: 220px; | |||
| flex: 1; | |||
| overflow: hidden; | |||
| max-height: 66px; | |||
| display: -webkit-box; | |||
| -webkit-line-clamp: 3; | |||
| -webkit-box-orient: vertical; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| @@ -461,6 +488,7 @@ export const XSText = styled(Typography)` | |||
| export const OfferImage = styled.img` | |||
| max-width: 72px; | |||
| max-height: 72px; | |||
| min-width: 72px; | |||
| width: 72px; | |||
| height: 72px; | |||
| border-radius: 4px; | |||
| @@ -475,3 +503,11 @@ export const Line = styled(Box)` | |||
| display: none; | |||
| } | |||
| `; | |||
| export const PhoneIcon = styled(Phone)` | |||
| @media (max-width: 600px) { | |||
| width: 14px; | |||
| height: 14px; | |||
| position: relative; | |||
| top: 1px; | |||
| } | |||
| `; | |||
| @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useFormik } from "formik"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { NavLink, useHistory } from "react-router-dom"; | |||
| import * as Yup from "yup"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { fetchLogin } from "../../../store/actions/login/loginActions"; | |||
| @@ -78,7 +78,7 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => { | |||
| historyRouter.location.pathname.length | |||
| ); | |||
| dispatch(fetchProfileOffers(userId)); | |||
| history.push({ | |||
| historyRouter.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| @@ -144,7 +144,7 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => { | |||
| }; | |||
| const submitOffer = (values) => { | |||
| dispatch(addOffer(values)); | |||
| dispatch(addOffer({ values, handleApiResponseSuccess })); | |||
| }; | |||
| const submitEditOffer = (id, values) => { | |||
| @@ -7,14 +7,15 @@ import Select from "../../Select/Select"; | |||
| export const ModalCreateOfferContainer = styled(Box)` | |||
| background-color: #fff; | |||
| position: fixed; | |||
| overflow-y: auto; | |||
| ${props => props.currentStep === 3 && `overflow-y: auto;`} | |||
| max-height: 90vh; | |||
| top: ${(props) => | |||
| props.currentStep === 1 ? "calc(50% - 345px);" : "calc(50% - 350px);"}; | |||
| props.currentStep === 1 ? "calc(50% - 400px);" : "calc(50% - 350px);"}; | |||
| left: ${(props) => | |||
| props.currentStep !== 3 ? "calc(50% - 310px);" : "calc(50% - 340px);"}; | |||
| props.currentStep !== 3 ? "calc(50% - 310px);" : "calc(50% - 420px);"}; | |||
| z-index: 150; | |||
| padding: ${(props) => (props.currentStep !== 3 ? "0 120px" : "0 130px")}; | |||
| overflow-y: auto; | |||
| &::-webkit-scrollbar { | |||
| width: 5px; | |||
| } | |||
| @@ -27,13 +28,21 @@ export const ModalCreateOfferContainer = styled(Box)` | |||
| scrollbar-width: thin; | |||
| scrollbar-color: #ddd; | |||
| @media (max-height: 820px) { | |||
| top: ${props => props.currentStep === 1 ? 'calc(50% - 340px)' : 'calc(50% - 340px)'}; | |||
| } | |||
| @media screen and (max-width: 628px) { | |||
| height: 740px; | |||
| width: 95%; | |||
| left: 10px; | |||
| height: 100vh; | |||
| max-height: 100vh; | |||
| min-height: 90vh; | |||
| width: 100vw; | |||
| top: 0; | |||
| left: 0; | |||
| padding: 0 30px; | |||
| top: 70px; | |||
| } | |||
| `; | |||
| export const ModalHeader = styled(Box)` | |||
| @@ -73,7 +82,7 @@ export const CloseIcon = styled(Box)` | |||
| export const CreateOfferContainer = styled(Container)` | |||
| margin-top: 0px; | |||
| display: flex; | |||
| width: ${props => props.currentStep === 3 ? "580px" : "380px"}; | |||
| width: ${(props) => (props.currentStep === 3 ? "580px" : "380px")}; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @@ -117,7 +126,7 @@ export const CreateOfferDescription = styled(Typography)` | |||
| export const CreateOfferFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 700px; | |||
| padding-top: 20px; | |||
| ${props => props.currentStep === 3 && `width: 120%; height: 420px;`} | |||
| `; | |||
| export const RegisterAltText = styled(Typography)` | |||
| font-family: "Poppins"; | |||
| @@ -143,6 +152,11 @@ export const FieldLabel = styled(Label)` | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| @media (max-width: 600px) { | |||
| & label { | |||
| font-size: 9px; | |||
| } | |||
| } | |||
| `; | |||
| export const SelectText = styled(Typography)` | |||
| font-size: 16px; | |||
| @@ -153,6 +167,10 @@ export const SelectField = styled(Select)` | |||
| position: relative; | |||
| top: 15px; | |||
| margin-bottom: 18px; | |||
| @media (max-width: 600px) { | |||
| height: 40px; | |||
| font-size: 12px; | |||
| } | |||
| & div { | |||
| ${SelectText} { | |||
| font-weight: 600; | |||
| @@ -6,12 +6,12 @@ import { | |||
| DescriptionField, | |||
| FieldLabel, | |||
| NextButton, | |||
| SelectOption, | |||
| TitleField, | |||
| } from "./FirstPartCreateOffer.styled"; | |||
| import * as Yup from "yup"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import Option from "../../../Select/Option/Option"; | |||
| import { SelectField } from "../CreateOffer.styled"; | |||
| import { useSelector } from "react-redux"; | |||
| import useScreenDimensions from "../../../../hooks/useScreenDimensions"; | |||
| @@ -72,52 +72,55 @@ const FirstPartCreateOffer = (props) => { | |||
| }; | |||
| return ( | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <> | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| {/* <Backdrop position="absolute" isLoading={isLoading} /> */} | |||
| <FieldLabel leftText={t("offer.title")} /> | |||
| <TitleField | |||
| name="nameOfProduct" | |||
| placeholder={t("offer.productName")} | |||
| italicPlaceholder | |||
| margin="normal" | |||
| value={formik.values.nameOfProduct} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.nameOfProduct && formik.errors.nameOfProduct} | |||
| helperText={formik.touched.nameOfProduct && formik.errors.nameOfProduct} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| <FieldLabel leftText={t("offer.productDescription")} /> | |||
| {dimensions.width > 600 ? ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| <FieldLabel leftText={t("offer.title")} /> | |||
| <TitleField | |||
| name="nameOfProduct" | |||
| placeholder={t("offer.productName")} | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| multiline | |||
| minRows={4} | |||
| height={"100px"} | |||
| /> | |||
| ) : ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| value={formik.values.nameOfProduct} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| error={formik.touched.nameOfProduct && formik.errors.nameOfProduct} | |||
| helperText={ | |||
| formik.touched.nameOfProduct && formik.errors.nameOfProduct | |||
| } | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| )} | |||
| <FieldLabel leftText={t("offer.productDescription")} /> | |||
| {dimensions.width > 600 ? ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| multiline | |||
| minRows={4} | |||
| height={"100px"} | |||
| /> | |||
| ) : ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| /> | |||
| )} | |||
| <FieldLabel leftText={t("offer.location")} /> | |||
| <SelectField | |||
| @@ -128,12 +131,12 @@ const FirstPartCreateOffer = (props) => { | |||
| formik.setFieldValue("location", value.target.value); | |||
| }} | |||
| > | |||
| <Option value="default">{t("offer.choseLocation")}</Option> | |||
| <SelectOption value="default">{t("offer.choseLocation")}</SelectOption> | |||
| {locations.map((loc) => { | |||
| return ( | |||
| <Option key={loc._if} value={loc.city}> | |||
| <SelectOption key={loc._if} value={loc.city}> | |||
| {loc.city} | |||
| </Option> | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </SelectField> | |||
| @@ -147,16 +150,16 @@ const FirstPartCreateOffer = (props) => { | |||
| formik.setFieldValue("category", value.target.value); | |||
| }} | |||
| > | |||
| <Option value="default">{t("offer.choseCategory")}</Option> | |||
| <SelectOption value="default">{t("offer.choseCategory")}</SelectOption> | |||
| {categories.map((cat, i) => { | |||
| return ( | |||
| <Option | |||
| <SelectOption | |||
| key={i} | |||
| value={cat.name} | |||
| onClick={() => handleSubcategories(cat.name)} | |||
| > | |||
| {cat.name} | |||
| </Option> | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </SelectField> | |||
| @@ -171,16 +174,17 @@ const FirstPartCreateOffer = (props) => { | |||
| formik.setFieldValue("subcategory", value.target.value); | |||
| }} | |||
| > | |||
| <Option value="default">{t("offer.choseSubcategory")}</Option> | |||
| <SelectOption value="default">{t("offer.choseSubcategory")}</SelectOption> | |||
| {subcat && | |||
| subcat.map((sub, i) => { | |||
| return ( | |||
| <Option key={i} value={sub}> | |||
| <SelectOption key={i} value={sub}> | |||
| {sub} | |||
| </Option> | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </SelectField> | |||
| </CreateOfferFormContainer> | |||
| <NextButton | |||
| type="submit" | |||
| @@ -189,14 +193,23 @@ const FirstPartCreateOffer = (props) => { | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| // disabled={ | |||
| // formik.values.username.length === 0 || | |||
| // formik.values.password.length === 0 | |||
| // } | |||
| onClick={formik.handleSubmit} | |||
| disabled={ | |||
| formik.values?.nameOfProduct?.length === 0 || | |||
| !formik.values?.nameOfProduct || | |||
| formik.values?.description?.length === 0 || | |||
| !formik.values?.description || | |||
| formik.values?.category?.length === 0 || | |||
| !formik.values?.category || | |||
| formik.values?.subcategory?.length === 0 || | |||
| !formik.values?.subcategory || | |||
| formik.values?.location?.length === 0 || | |||
| !formik.values?.location | |||
| } | |||
| > | |||
| {t("offer.continue")} | |||
| </NextButton> | |||
| </CreateOfferFormContainer> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| ` | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState, useEffect } from "react"; | |||
| import React, { useMemo, useState, useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CreateOfferFormContainer, | |||
| @@ -9,9 +9,11 @@ import { | |||
| } from "./SecondPartCreateOffer.styled"; | |||
| import ImagePicker from "../../../ImagePicker/ImagePicker"; | |||
| import { useTranslation, Trans } from "react-i18next"; | |||
| import Option from "../../../Select/Option/Option"; | |||
| import { SelectAltText, SelectField, SelectText } from "../CreateOffer.styled"; | |||
| import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled"; | |||
| import { | |||
| NextButton, | |||
| SelectOption, | |||
| } from "../FirstPart/FirstPartCreateOffer.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { conditionSelectEnum } from "../../../../enums/conditionEnum"; | |||
| import { useFormik } from "formik"; | |||
| @@ -61,10 +63,17 @@ const SecondPartCreateOffer = (props) => { | |||
| }; | |||
| const { t } = useTranslation(); | |||
| let imagesEmpty = 0; | |||
| images.forEach((item) => { | |||
| if (item === null || item === undefined) imagesEmpty++; | |||
| }); | |||
| const imagesEmpty = useMemo(() => { | |||
| let numOfImagesEmpty = 0; | |||
| images.forEach((item) => { | |||
| if (item === null || item === undefined) numOfImagesEmpty++; | |||
| }); | |||
| return numOfImagesEmpty; | |||
| }, [images]); | |||
| // for (let i = 0; i < numberOfImages; i++) { | |||
| // let item = images[i]; | |||
| // if (item === null || item === undefined) imagesEmpty++; | |||
| // } | |||
| const handleSubmit = (values) => { | |||
| props.handleNext(values); | |||
| @@ -84,21 +93,21 @@ const SecondPartCreateOffer = (props) => { | |||
| console.log("slike", images); | |||
| return ( | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| <Scroller> | |||
| {images.map((item, index) => { | |||
| console.log(item); | |||
| return ( | |||
| <ImagePicker | |||
| key={index} | |||
| image={item} | |||
| setImage={(image) => setImage(index, image)} | |||
| deleteImage={() => setImage(index, null)} | |||
| showDeleteIcon | |||
| /> | |||
| ); | |||
| })} | |||
| {/* {props.offer === undefined | |||
| <> | |||
| <CreateOfferFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| <Scroller> | |||
| {images.map((item, index) => { | |||
| return ( | |||
| <ImagePicker | |||
| key={index} | |||
| image={item} | |||
| setImage={(image) => setImage(index, image)} | |||
| deleteImage={() => setImage(index, null)} | |||
| showDeleteIcon | |||
| /> | |||
| ); | |||
| })} | |||
| {/* {props.offer === undefined | |||
| ? images.map((item, index) => ( | |||
| <ImagePicker | |||
| key={index} | |||
| @@ -117,48 +126,51 @@ const SecondPartCreateOffer = (props) => { | |||
| showDeleteIcon | |||
| /> | |||
| ))} */} | |||
| </Scroller> | |||
| <SupportedFormats> | |||
| <Trans i18nKey="offer.supportedImagesFormats" /> | |||
| </SupportedFormats> | |||
| <InputButtonContainer> | |||
| <FieldLabel leftText={t("offer.condition")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.condition | |||
| } | |||
| onChange={(value) => { | |||
| formik.setFieldValue("condition", value.target.value); | |||
| }} | |||
| > | |||
| <Option value="default">{t("offer.choseCondition")}</Option> | |||
| {Object.keys(conditionSelectEnum).map((key) => { | |||
| var item = conditionSelectEnum[key]; | |||
| return ( | |||
| <Option value={item.mainText} key={item.value}> | |||
| <SelectText>{item.mainText}</SelectText> | |||
| <SelectAltText>{item.altText}</SelectAltText> | |||
| </Option> | |||
| ); | |||
| })} | |||
| </SelectField> | |||
| <NextButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| // disabled={imagesEmpty === numberOfImages} | |||
| disabled={ | |||
| props.offer === undefined ? imagesEmpty === numberOfImages : false | |||
| } | |||
| > | |||
| {t("offer.continue")} | |||
| </NextButton> | |||
| </InputButtonContainer> | |||
| </CreateOfferFormContainer> | |||
| </Scroller> | |||
| <SupportedFormats> | |||
| <Trans i18nKey="offer.supportedImagesFormats" /> | |||
| </SupportedFormats> | |||
| <InputButtonContainer> | |||
| <FieldLabel leftText={t("offer.condition")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.condition | |||
| } | |||
| onChange={(value) => { | |||
| formik.setFieldValue("condition", value.target.value); | |||
| }} | |||
| > | |||
| <SelectOption value="default"> | |||
| {t("offer.choseCondition")} | |||
| </SelectOption> | |||
| {Object.keys(conditionSelectEnum).map((key) => { | |||
| var item = conditionSelectEnum[key]; | |||
| return ( | |||
| <SelectOption value={item.mainText} key={item.value}> | |||
| <SelectText>{item.mainText}</SelectText> | |||
| <SelectAltText>{item.altText}</SelectAltText> | |||
| </SelectOption> | |||
| ); | |||
| })} | |||
| </SelectField> | |||
| </InputButtonContainer> | |||
| </CreateOfferFormContainer> | |||
| <NextButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| onClick={formik.handleSubmit} | |||
| // disabled={imagesEmpty === numberOfImages} | |||
| disabled={ | |||
| props.offer === undefined ? imagesEmpty === numberOfImages : false | |||
| } | |||
| > | |||
| {t("offer.continue")} | |||
| </NextButton> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -1,11 +1,16 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CreateOfferFormContainer, | |||
| // CreateOfferFormContainer, | |||
| PreviewCard, | |||
| } from "./ThirdPartCreateOffer.styled"; | |||
| import { NextButton } from "../FirstPart/FirstPartCreateOffer.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { CreateOfferFormContainer } from "../CreateOffer.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const ThirdPartCreateOffer = (props) => { | |||
| const {t} = useTranslation(); | |||
| const offer = { | |||
| offer: { | |||
| category: { | |||
| @@ -25,9 +30,32 @@ const ThirdPartCreateOffer = (props) => { | |||
| }; | |||
| return ( | |||
| <CreateOfferFormContainer component="form" onSubmit={handleSubmit}> | |||
| <PreviewCard offer={offer} showBarterButton={false} showPublishButton /> | |||
| </CreateOfferFormContainer> | |||
| <> | |||
| <CreateOfferFormContainer currentStep={3} component="form" onSubmit={handleSubmit}> | |||
| <PreviewCard | |||
| offer={offer} | |||
| showBarterButton={false} | |||
| showPublishButton={false} | |||
| showExchangeButton={false} | |||
| hideViews | |||
| /> | |||
| </CreateOfferFormContainer> | |||
| <NextButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| fullWidth | |||
| onClick={handleSubmit} | |||
| // disabled={ | |||
| // formik.values.username.length === 0 || | |||
| // formik.values.password.length === 0 | |||
| // } | |||
| > | |||
| {t("offer.publish")} | |||
| </NextButton> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -45,7 +45,6 @@ const FilterCheckboxDropdown = (props) => { | |||
| }, [props.filters]) | |||
| const handleChange = (item) => { | |||
| console.log(item); | |||
| if (props.oneValueAllowed) { | |||
| props.setItemsSelected([item]); | |||
| } else { | |||
| @@ -40,7 +40,6 @@ const FilterRadioDropdown = (props) => { | |||
| } | |||
| }, [props.selected]) | |||
| console.log(props.selected) | |||
| const handleClear = () => { | |||
| setToSearch(""); | |||
| @@ -1,4 +1,4 @@ | |||
| import React from "react"; | |||
| import React, { useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CheckButton, | |||
| @@ -16,18 +16,35 @@ import { | |||
| OfferDetails, | |||
| OfferImage, | |||
| Scroller, | |||
| PublishButtonContainer, | |||
| } from "./ItemDetailsCard.styled"; | |||
| import { NextButton } from "../CreateOfferCard/FirstPart/FirstPartCreateOffer.styled"; | |||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | |||
| import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg"; | |||
| import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg"; | |||
| import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { increaseCounter } from "../../../store/actions/counter/counterActions"; | |||
| import _ from "lodash"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| const ItemDetailsCard = (props) => { | |||
| const offer = props.offer; | |||
| const history = useHistory(); | |||
| const chats = useSelector(selectLatestChats); | |||
| const userId = useSelector(selectUserId); | |||
| const dispatch = useDispatch(); | |||
| const dateCreated = new Date(offer?.offer?._created); | |||
| useEffect(() => { | |||
| if (offer?.offer?._id) { | |||
| _.once(function () { | |||
| dispatch(increaseCounter(offer?.offer?._id)); | |||
| })(); | |||
| } | |||
| }, [offer]); | |||
| const dayCreated = | |||
| dateCreated.getDate() < 10 | |||
| ? "0" + dateCreated.getDate() | |||
| @@ -37,6 +54,20 @@ const ItemDetailsCard = (props) => { | |||
| ? "0" + (dateCreated.getMonth() + 1) | |||
| : dateCreated.getMonth() + 1; | |||
| const yearCreated = dateCreated.getFullYear(); | |||
| const startExchange = () => { | |||
| const chatItem = chats.find( | |||
| (item) => item.chat.offerId === offer?.offer?._id | |||
| ); | |||
| if (chatItem !== undefined) { | |||
| history.push(`/messages/${chatItem.chat._id}`); | |||
| } else { | |||
| if (offer?.offer?.userId !== userId) { | |||
| history.push(`/messages/newMessage`, { | |||
| offerId: offer?.offer?._id, | |||
| }); | |||
| } | |||
| } | |||
| }; | |||
| return ( | |||
| <ItemDetailsCardContainer | |||
| sponsored={props.sponsored.toString()} | |||
| @@ -75,12 +106,12 @@ const ItemDetailsCard = (props) => { | |||
| </InfoIcon> | |||
| <InfoText>{offer?.offer?.condition}</InfoText> | |||
| </InfoGroup> | |||
| {!props.showPublishButton && ( | |||
| {!props.hideViews && ( | |||
| <InfoGroup views> | |||
| <InfoIcon color={"black"} component="span" size="12px" last> | |||
| <Eye width={"18px"} height={"20px"} /> | |||
| </InfoIcon> | |||
| <InfoText>{offer?.offer?.views?.viewers?.length}</InfoText> | |||
| <InfoText>{offer?.offer?.views?.count}</InfoText> | |||
| </InfoGroup> | |||
| )} | |||
| </Info> | |||
| @@ -88,7 +119,10 @@ const ItemDetailsCard = (props) => { | |||
| {dayCreated}.{monthCreated}.{yearCreated} | |||
| </PostDate> | |||
| </OfferInfo> | |||
| <Details hasScrollBar={!props.showPublishButton}> | |||
| <Details | |||
| hasScrollBar={!props.showPublishButton} | |||
| exchange={props.showExchangeButton} | |||
| > | |||
| <OfferTitle>{offer?.offer?.name}</OfferTitle> | |||
| <Scroller> | |||
| {offer?.offer?.images?.map((item) => { | |||
| @@ -97,18 +131,19 @@ const ItemDetailsCard = (props) => { | |||
| </Scroller> | |||
| <OfferDetails> | |||
| <OfferDescriptionTitle>Opis:</OfferDescriptionTitle> | |||
| <OfferDescriptionText showBarterButton={props.showBarterButton}> | |||
| <OfferDescriptionText showBarterButton={props.showExchangeButton}> | |||
| {offer?.offer?.description} | |||
| </OfferDescriptionText> | |||
| </OfferDetails> | |||
| </Details> | |||
| {!props.halfwidth && !props.showPublishButton ? ( | |||
| {!props.halfwidth && props.showExchangeButton ? ( | |||
| <React.Fragment> | |||
| <CheckButton | |||
| variant={props.sponsored ? "contained" : "outlined"} | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor={props.sponsored ? "white" : selectedTheme.primaryPurple} | |||
| style={{ fontWeight: "600" }} | |||
| onClick={startExchange} | |||
| > | |||
| Trampi | |||
| </CheckButton> | |||
| @@ -116,24 +151,6 @@ const ItemDetailsCard = (props) => { | |||
| ) : ( | |||
| <></> | |||
| )} | |||
| {props.showPublishButton && ( | |||
| <PublishButtonContainer> | |||
| <NextButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| width="350px" | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor="white" | |||
| // disabled={ | |||
| // formik.values.username.length === 0 || | |||
| // formik.values.password.length === 0 | |||
| // } | |||
| > | |||
| OBJAVI | |||
| </NextButton> | |||
| </PublishButtonContainer> | |||
| )} | |||
| </ItemDetailsCardContainer> | |||
| ); | |||
| }; | |||
| @@ -156,6 +173,8 @@ ItemDetailsCard.propTypes = { | |||
| halfwidth: PropTypes.bool, | |||
| sponsored: PropTypes.bool, | |||
| offer: PropTypes.any, | |||
| hideViews: PropTypes.bool, | |||
| showExchangeButton: PropTypes.bool, | |||
| // offer: PropTypes.shape({ | |||
| // images: PropTypes.any, | |||
| // name:PropTypes.string, | |||
| @@ -175,6 +194,7 @@ ItemDetailsCard.propTypes = { | |||
| ItemDetailsCard.defaultProps = { | |||
| halfwidth: false, | |||
| sponsored: false, | |||
| showExchangeButton: true, | |||
| }; | |||
| export default ItemDetailsCard; | |||
| @@ -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` | |||
| @@ -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 | |||
| @@ -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; | |||
| ` | |||
| @@ -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; | |||
| @@ -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; | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| `; | |||
| @@ -27,6 +27,8 @@ import { | |||
| OfferViews, | |||
| RemoveIcon, | |||
| RemoveIconContainer, | |||
| StarIcon, | |||
| StarIconContainer, | |||
| } from "./OfferCard.styled"; | |||
| import DeleteOffer from "./DeleteOffer"; | |||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | |||
| @@ -43,6 +45,14 @@ const OfferCard = (props) => { | |||
| const routeToItem = (itemId) => { | |||
| history.push(`/proizvodi/${itemId}`); | |||
| }; | |||
| const messageUser = () => { | |||
| props.messageUser(props.offer); | |||
| }; | |||
| const makeReview = () => { | |||
| if (!props.disabledReviews) { | |||
| props.makeReview(props.offer); | |||
| } | |||
| }; | |||
| const closeModalHandler = () => { | |||
| setDeleteOfferModal(false); | |||
| @@ -58,15 +68,14 @@ const OfferCard = (props) => { | |||
| document.body.style.overflow = "auto"; | |||
| } | |||
| console.log(props.offer); | |||
| return ( | |||
| <React.Fragment> | |||
| <OfferCardContainer | |||
| vertical={props.vertical} | |||
| sponsored={ | |||
| props.pinned !== undefined | |||
| ? props.pinned.toString() | |||
| : props?.offer?.pinned.toString() | |||
| props?.pinned !== undefined | |||
| ? props?.pinned?.toString() | |||
| : props?.offer?.pinned?.toString() | |||
| } | |||
| halfwidth={props.halfwidth ? 1 : 0} | |||
| > | |||
| @@ -79,7 +88,7 @@ const OfferCard = (props) => { | |||
| <OfferFlexContainer vertical={props.vertical}> | |||
| <OfferImageContainer vertical={props.vertical}> | |||
| <OfferImage | |||
| src={props?.offer?.images[0]} | |||
| src={props?.offer?.images ? props?.offer?.images[0] : ""} | |||
| vertical={props.vertical} | |||
| ></OfferImage> | |||
| </OfferImageContainer> | |||
| @@ -103,14 +112,14 @@ const OfferCard = (props) => { | |||
| <DetailIcon color="black" component="span" size="16px"> | |||
| <Category width={"14px"} /> | |||
| </DetailIcon> | |||
| <DetailText>{props?.offer?.category.name}</DetailText> | |||
| <DetailText>{props?.offer?.category?.name}</DetailText> | |||
| </OfferCategory> | |||
| <OfferViews vertical={props.vertical}> | |||
| {props.dontShowViews ? (<></>) : (<OfferViews vertical={props.vertical}> | |||
| <DetailIcon color="black" component="span" size="16px"> | |||
| <EyeIcon /> | |||
| </DetailIcon> | |||
| <DetailText>{props?.offer?.views?.viewers?.length}</DetailText> | |||
| </OfferViews> | |||
| <DetailText>{props?.offer?.views?.count}</DetailText> | |||
| </OfferViews>)} | |||
| </OfferDetails> | |||
| </OfferInfo> | |||
| {!props.halfwidth ? ( | |||
| @@ -154,8 +163,15 @@ const OfferCard = (props) => { | |||
| <EditIcon /> | |||
| </EditIconContainer> | |||
| </> | |||
| ) : props.aboveChat ? ( | |||
| <StarIconContainer | |||
| disabled={props.disabledReviews} | |||
| onClick={makeReview} | |||
| > | |||
| <StarIcon disabled={props.disabledReviews} /> | |||
| </StarIconContainer> | |||
| ) : ( | |||
| <MessageIcon vertical={props.vertical}> | |||
| <MessageIcon vertical={props.vertical} onClick={messageUser}> | |||
| <Message /> | |||
| </MessageIcon> | |||
| )} | |||
| @@ -196,10 +212,17 @@ OfferCard.propTypes = { | |||
| pinned: PropTypes.bool, | |||
| vertical: PropTypes.bool, | |||
| isMyOffer: PropTypes.bool, | |||
| aboveChat: PropTypes.bool, | |||
| disabledReviews: PropTypes.bool, | |||
| messageUser: PropTypes.func, | |||
| makeReview: PropTypes.func, | |||
| dontShowViews: PropTypes.bool, | |||
| }; | |||
| OfferCard.defaultProps = { | |||
| halfwidth: false, | |||
| sponsored: false, | |||
| messageUser: () => {}, | |||
| makeReview: () => {}, | |||
| }; | |||
| export default OfferCard; | |||
| @@ -7,6 +7,7 @@ import { Icon } from "../../Icon/Icon"; | |||
| import { ReactComponent as Eye } from "../../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as Remove } from "../../../assets/images/svg/trash.svg"; | |||
| import { ReactComponent as Edit } from "../../../assets/images/svg/edit.svg"; | |||
| import { ReactComponent as Star } from "../../../assets/images/svg/star.svg"; | |||
| export const OfferCardContainer = styled(Container)` | |||
| display: flex; | |||
| @@ -331,12 +332,22 @@ export const RemoveIconContainer = styled(MessageIcon)` | |||
| export const RemoveIcon = styled(Remove)``; | |||
| export const EditIconContainer = styled(MessageIcon)` | |||
| right: 70px; | |||
| @media screen and (max-width: 600px) { | |||
| position: absolute; | |||
| display: block; | |||
| right: 20px; | |||
| top: 60%; | |||
| } | |||
| `; | |||
| export const EditIcon = styled(Edit)``; | |||
| export const StarIconContainer = styled(MessageIcon)` | |||
| opacity: ${props => props.disabled ? "0.4" : "1"}; | |||
| ${props => props.disabled && ` | |||
| cursor: initial; | |||
| & button { | |||
| cursor: initial; | |||
| } | |||
| `} | |||
| `; | |||
| export const StarIcon = styled(Star)` | |||
| & path { | |||
| stroke: ${(props) => | |||
| props.disabled | |||
| ? selectedTheme.primaryPurpleDisabled | |||
| : selectedTheme.primaryPurple}; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,132 @@ | |||
| import React, { useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ProfileImage, | |||
| ProfileImageContainer, | |||
| ProfileName, | |||
| ReviewContainer, | |||
| ReviewDetails, | |||
| ReviewDetailsText, | |||
| ReviewDetailsValue, | |||
| ReviewQuote, | |||
| ReviewQuoteBox, | |||
| ReviewQuoteText, | |||
| ThumbBox, | |||
| ThumbDown, | |||
| ThumbUp, | |||
| } from "./UserReviewsCard.styled"; | |||
| import { ListItem } from "@mui/material"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { reviewEnum } from "../../../enums/reviewEnum"; | |||
| // import { useDispatch } from "react-redux"; | |||
| // import { fetchProfile } from "../../../store/actions/profile/profileActions"; | |||
| const UserReviewsCard = (props) => { | |||
| const { t } = useTranslation(); | |||
| // const dispatch = useDispatch(); | |||
| // useEffect(() => { | |||
| // if (props.review?.userId) { | |||
| // dispatch(fetchProfile(props.review.userId)); | |||
| // } | |||
| // }, [props.review?.userId]) | |||
| console.log(props); | |||
| const review = useMemo(() => { | |||
| if (props.givingReview) { | |||
| return { | |||
| ...props.review | |||
| } | |||
| } | |||
| let isSuccessfulSwap = "DA"; | |||
| if (props.review.succeeded === "failed") isSuccessfulSwap = "NE"; | |||
| let isGoodCommunication = "DA"; | |||
| if (props.review.communication === "could be better") isGoodCommunication = "MOŽE BOLJE"; | |||
| if (props.review.communication === "no") isGoodCommunication = "NE"; | |||
| return { | |||
| name: props.review.companyName, | |||
| image: props.review.image, | |||
| isGoodCommunication, | |||
| isSuccessfulSwap, | |||
| quote: props?.review?.message | |||
| } | |||
| }, [props.review]); | |||
| const isGood = useMemo(() => { | |||
| if ( | |||
| review?.isGoodCommunication === reviewEnum.NO.mainText || | |||
| review?.isSuccessfulSwap === reviewEnum.NO.mainText | |||
| ) { | |||
| return false; | |||
| } | |||
| return true; | |||
| }, [review]); | |||
| return ( | |||
| <ReviewContainer key={review?.image}> | |||
| <ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}> | |||
| <ProfileImageContainer> | |||
| <ProfileImage alt={review?.name} src={review?.image} /> | |||
| </ProfileImageContainer> | |||
| <ProfileName sx={{ color: selectedTheme.primaryPurple }}> | |||
| <b>{review?.name}</b> | |||
| </ProfileName> | |||
| </ListItem> | |||
| <ReviewQuote | |||
| container | |||
| direction="row" | |||
| justifyContent="start" | |||
| alignItems="center" | |||
| spacing={2} | |||
| sx={{ pl: 2, py: 2 }} | |||
| > | |||
| <ThumbBox item> | |||
| {isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />} | |||
| </ThumbBox> | |||
| <ReviewQuoteBox item> | |||
| <ReviewQuoteText | |||
| sx={{ display: "inline" }} | |||
| component="span" | |||
| variant="body2" | |||
| color="text.primary" | |||
| > | |||
| "{review?.quote}" | |||
| </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; | |||
| @@ -2,13 +2,14 @@ import styled from "styled-components"; | |||
| import { List, Box, Typography, Grid } from "@mui/material"; | |||
| import ThumbUpIcon from "@mui/icons-material/ThumbUp"; | |||
| import ThumbDownIcon from "@mui/icons-material/ThumbDown"; | |||
| import selectedTheme from "../../themes"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const ReviewsBox = styled(Box)` | |||
| width: 100%; | |||
| height: calc(100% - 90px); | |||
| max-height: 100vh; | |||
| @media (max-width: 1200px) { | |||
| padding: 0 50px; | |||
| padding: 0; | |||
| } | |||
| @media (max-width: 600px) { | |||
| position: relative; | |||
| @@ -82,3 +83,49 @@ export const NoReviewsAltText = styled(Typography)` | |||
| width: 100%; | |||
| margin-bottom: 36px; | |||
| ` | |||
| export const ProfileImage = styled.img` | |||
| width: 54px; | |||
| height: 54px; | |||
| border-radius: 100%; | |||
| ` | |||
| export const ProfileImageContainer = styled(Box)` | |||
| width: 54px; | |||
| height: 54px; | |||
| border-radius: 100%; | |||
| margin-right: 14px; | |||
| ` | |||
| export const ReviewQuote = styled(Grid)` | |||
| position: relative; | |||
| left: 8px; | |||
| ` | |||
| export const ThumbBox = styled(Grid)` | |||
| max-width: 20px; | |||
| ` | |||
| export const ReviewQuoteBox = styled(Grid)` | |||
| ` | |||
| export const ReviewQuoteText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| ` | |||
| export const ReviewDetails = styled(Grid)` | |||
| ` | |||
| export const ReviewDetailsText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| font-style: italic; | |||
| letter-spacing: 0.02em; | |||
| ` | |||
| export const ReviewDetailsValue = styled(Typography)` | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-style: normal; | |||
| font-weight: 600; | |||
| ` | |||
| export const ProfileName = styled(Typography)` | |||
| font-weight: 600; | |||
| font-size: 16px; | |||
| font-family: "Open Sans"; | |||
| ` | |||
| export const ReviewContainer = styled(Box)` | |||
| ` | |||
| @@ -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> | |||
| ); | |||
| @@ -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; | |||
| } | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| ` | |||
| @@ -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 | |||
| @@ -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}; | |||
| `; | |||
| @@ -0,0 +1,84 @@ | |||
| import React, { useEffect, useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { DirectChatContainer } from "./DirectChat.styled"; | |||
| import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle"; | |||
| import DirectChatHeader from "./DirectChatHeader/DirectChatHeader"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| // import { fetchOneOffer } from "../../store/actions/offers/offersActions"; | |||
| import { useLocation, useRouteMatch } from "react-router-dom"; | |||
| import { fetchOneChat } from "../../store/actions/chat/chatActions"; | |||
| import { | |||
| // selectLatestChats, | |||
| selectSelectedChat, | |||
| } from "../../store/selectors/chatSelectors"; | |||
| import DirectChatContent from "./DirectChatContent/DirectChatContent"; | |||
| import { selectOffer } from "../../store/selectors/offersSelectors"; | |||
| import { fetchOneOffer } from "../../store/actions/offers/offersActions"; | |||
| const DirectChat = () => { | |||
| const chat = useSelector(selectSelectedChat); | |||
| const offer = useSelector(selectOffer); | |||
| const routeMatch = useRouteMatch(); | |||
| const location = useLocation(); | |||
| // const allChats = useSelector(selectLatestChats); | |||
| // const foundChat = useMemo( | |||
| // () => allChats.find((item) => item?.chat?._id === chat?.chat?._id), | |||
| // [chat, allChats] | |||
| // ); | |||
| const offerObject = useMemo(() => { | |||
| if (location?.state?.offerId) { | |||
| return offer?.offer; | |||
| } | |||
| return chat?.offer?.offer; | |||
| }, [chat, location.state, offer]); | |||
| const chatObject = useMemo(() => { | |||
| if (location?.state?.offerId) { | |||
| return {}; | |||
| } | |||
| return chat?.chat; | |||
| }, [chat, location.state]); | |||
| const interlocutorObject = useMemo(() => { | |||
| if (location?.state?.offerId) { | |||
| return { | |||
| image: offer?.companyData?.image, | |||
| name: offer?.companyData?.company?.name, | |||
| location: offer?.companyData?.company?.contacts?.location | |||
| } | |||
| } | |||
| return chat?.interlocutor; | |||
| }, [chat,location.state, offer]); | |||
| console.log("offerObject: ", offerObject); | |||
| console.log("chatObject: ", chatObject); | |||
| console.log("interlucatorObject: ", interlocutorObject); | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| console.log(location.state) | |||
| if (routeMatch.params.idChat && location.state?.offerId) { | |||
| refreshChat(); | |||
| } | |||
| }, [routeMatch.params.idChat, location.state?.offerId]); | |||
| const refreshChat = () => { | |||
| if (routeMatch.params.idChat === "newMessage") { | |||
| dispatch(fetchOneOffer(location.state.offerId)) | |||
| } else { | |||
| dispatch(fetchOneChat(routeMatch.params.idChat)); | |||
| } | |||
| }; | |||
| return ( | |||
| <DirectChatContainer> | |||
| <DirectChatHeaderTitle /> | |||
| <DirectChatHeader offer={offerObject} interlocutor={interlocutorObject} /> | |||
| <DirectChatContent | |||
| chat={chatObject} | |||
| interlucator={interlocutorObject} | |||
| refreshChat={refreshChat} | |||
| /> | |||
| </DirectChatContainer> | |||
| ); | |||
| }; | |||
| DirectChat.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default DirectChat; | |||
| @@ -0,0 +1,5 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const DirectChatContainer = styled(Box)` | |||
| ` | |||
| @@ -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; | |||
| @@ -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)``; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| ` | |||
| @@ -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; | |||
| @@ -0,0 +1,6 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const DirectChatHeaderContainer = styled(Box)` | |||
| ` | |||
| @@ -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 | |||
| @@ -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; | |||
| ` | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| `; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| ` | |||
| @@ -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 | |||
| @@ -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; | |||
| ` | |||
| @@ -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, | |||
| }; | |||
| @@ -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%; | |||
| ` | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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, | |||
| @@ -1,32 +1,28 @@ | |||
| import React, { useMemo } from 'react'; | |||
| import React, { useMemo } from "react"; | |||
| import Header from "./Header/Header"; | |||
| import { useSelector } from "react-redux"; | |||
| import { ItemDetailsContainer } from "./ItemDetails.styled"; | |||
| import ItemDetailsCard from "../Cards/ItemDetailsCard/ItemDetailsCard"; | |||
| import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard"; | |||
| import { selectOffer } from '../../store/selectors/offersSelectors'; | |||
| import { selectUserId } from '../../store/selectors/loginSelectors'; | |||
| // import { useHistory } from 'react-router-dom'; | |||
| import { selectOffer } from "../../store/selectors/offersSelectors"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| const ItemDetails = () => { | |||
| const offer = useSelector(selectOffer); | |||
| const userId = useSelector(selectUserId); | |||
| let isMyProfile = useMemo(() => { | |||
| if (offer?.offer?.userId?.toString() === userId.toString()) { | |||
| return true; | |||
| } | |||
| return false; | |||
| }, [offer, userId]) | |||
| console.log(isMyProfile) | |||
| return ( | |||
| <ItemDetailsContainer> | |||
| <Header/> | |||
| <ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} /> | |||
| <ItemDetailsCard offer={offer} isMyOffer={isMyProfile}/> | |||
| </ItemDetailsContainer> | |||
| ) | |||
| } | |||
| const offer = useSelector(selectOffer); | |||
| const userId = useSelector(selectUserId); | |||
| let isMyProfile = useMemo(() => { | |||
| if (offer?.offer?.userId?.toString() === userId.toString()) { | |||
| return true; | |||
| } | |||
| return false; | |||
| }, [offer, userId]); | |||
| return ( | |||
| <ItemDetailsContainer> | |||
| <Header /> | |||
| <ItemDetailsHeaderCard offer={offer} isMyProfile={isMyProfile} /> | |||
| <ItemDetailsCard offer={offer} isMyOffer={isMyProfile} /> | |||
| </ItemDetailsContainer> | |||
| ); | |||
| }; | |||
| export default ItemDetails; | |||
| @@ -22,10 +22,15 @@ import { ReactComponent as PIB } from "../../../assets/images/svg/pib.svg"; | |||
| import { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| const ItemDetailsHeaderCard = (props) => { | |||
| const history = useHistory(); | |||
| const chats = useSelector(selectLatestChats); | |||
| const offer = props.offer; | |||
| const userId = useSelector(selectUserId); | |||
| if (!props.offer) { | |||
| return <div>Loading...</div>; | |||
| } | |||
| @@ -42,6 +47,20 @@ const ItemDetailsHeaderCard = (props) => { | |||
| const handleGoProfile = () => { | |||
| history.push(`/profile/${offer?.offer?.userId}`); | |||
| }; | |||
| const messageUser = (offer) => { | |||
| const chatItem = chats.find( | |||
| (item) => item.chat.offerId === offer?.offer?._id | |||
| ); | |||
| if (chatItem !== undefined) { | |||
| history.push(`/messages/${chatItem.chat._id}`); | |||
| } else { | |||
| if (offer?.offer?.userId !== userId) { | |||
| history.push(`/messages/newMessage`, { | |||
| offerId: offer?.offer?._id, | |||
| }); | |||
| } | |||
| } | |||
| }; | |||
| return ( | |||
| <ItemDetailsHeaderContainer | |||
| isMyProfile={props.isMyProfile} | |||
| @@ -79,7 +98,7 @@ const ItemDetailsHeaderCard = (props) => { | |||
| <UserIcon /> | |||
| </UserIconContainer> | |||
| ) : ( | |||
| <MessageIcon> | |||
| <MessageIcon onClick={() => messageUser(offer)}> | |||
| <MessageColor /> | |||
| </MessageIcon> | |||
| )} | |||
| @@ -121,17 +140,6 @@ ItemDetailsHeaderCard.propTypes = { | |||
| sponsored: PropTypes.bool, | |||
| offer: PropTypes.any, | |||
| isMyProfile: PropTypes.bool, | |||
| // offer: PropTypes.shape({ | |||
| // images: PropTypes.any, | |||
| // name:PropTypes.string, | |||
| // description:PropTypes.string, | |||
| // category:PropTypes.shape({ | |||
| // name:PropTypes.string | |||
| // }), | |||
| // location:PropTypes.shape({ | |||
| // city:PropTypes.string | |||
| // }) | |||
| // }) | |||
| }; | |||
| ItemDetailsHeaderCard.defaultProps = { | |||
| halfwidth: false, | |||
| @@ -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, | |||
| @@ -4,6 +4,7 @@ import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import Option from "../../Select/Option/Option"; | |||
| import Select from "../../Select/Select"; | |||
| import {ReactComponent as Refresh} from "../../../assets/images/svg/refresh.svg" | |||
| export const HeaderContainer = styled(Box)` | |||
| margin-top: 20px; | |||
| @@ -93,3 +94,20 @@ export const HeaderAltLocation = styled(Typography)` | |||
| display: none; | |||
| } | |||
| ` | |||
| export const RefreshIcon = styled(Refresh)` | |||
| width: 18px; | |||
| height: 18px; | |||
| position: relative; | |||
| top: 3px; | |||
| left: -5px; | |||
| & path { | |||
| stroke: ${selectedTheme.primaryDarkTextThird}; | |||
| } | |||
| ` | |||
| export const MySwapsTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.primaryDarkTextThird}; | |||
| position: relative; | |||
| left: 9px; | |||
| ` | |||
| @@ -4,19 +4,20 @@ import { MarketPlaceContainer } from "./MarketPlace.styled"; | |||
| import Header from "./Header/Header"; | |||
| import Offers from "./Offers/Offers"; | |||
| const MarketPlace = () => { | |||
| const MarketPlace = (props) => { | |||
| const [isGrid, setIsGrid] = useState(false); | |||
| return ( | |||
| <MarketPlaceContainer> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} /> | |||
| <Offers isGrid={isGrid} /> | |||
| <Header isGrid={isGrid} setIsGrid={setIsGrid} myOffers={props.myOffers} /> | |||
| <Offers isGrid={isGrid} myOffers={props.myOffers} /> | |||
| </MarketPlaceContainer> | |||
| ); | |||
| }; | |||
| MarketPlace.propTypes = { | |||
| children: PropTypes.node, | |||
| myOffers: PropTypes.bool, | |||
| }; | |||
| export default MarketPlace; | |||
| @@ -1,10 +1,14 @@ | |||
| import React, { useEffect, useRef, useState } from "react"; | |||
| import React, { useEffect, useMemo, useRef, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { OffersContainer } from "./Offers.styled"; | |||
| import OfferCard from "../../Cards/OfferCard/OfferCard"; | |||
| import { fetchOffers } from "../../../store/actions/offers/offersActions"; | |||
| import { | |||
| fetchMineOffers, | |||
| fetchOffers, | |||
| } from "../../../store/actions/offers/offersActions"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { | |||
| selectMineOffers, | |||
| selectOffers, | |||
| selectPinnedOffers, | |||
| selectTotalOffers, | |||
| @@ -14,16 +18,39 @@ import { HOME_PAGE } from "../../../constants/pages"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { useQueryString } from "../../../hooks/useQueryString"; | |||
| import OffersNotFound from "./OffersNotFound"; | |||
| import { | |||
| selectSelectedCategory, | |||
| selectSelectedLocations, | |||
| selectSelectedSortOption, | |||
| selectSelectedSubcategory, | |||
| } from "../../../store/selectors/filtersSelectors"; | |||
| import { sortEnum } from "../../../enums/sortEnum"; | |||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { fetchChats } from "../../../store/actions/chat/chatActions"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| const Offers = (props) => { | |||
| const [page, setPage] = useState(1); | |||
| // const [pinnedLength, setPinnedLength] = useState(0); | |||
| const pinnedOffers = useSelector(selectPinnedOffers); | |||
| const selectedCategory = useSelector(selectSelectedCategory); | |||
| const selectedSubcategory = useSelector(selectSelectedSubcategory); | |||
| const selectedLocations = useSelector(selectSelectedLocations); | |||
| const selectedSortOption = useSelector(selectSelectedSortOption); | |||
| const offers = useSelector(selectOffers); | |||
| const mineOffers = useSelector(selectMineOffers); | |||
| const chats = useSelector(selectLatestChats); | |||
| const total = useSelector(selectTotalOffers); | |||
| const history = useHistory(); | |||
| const dispatch = useDispatch(); | |||
| const offersRef = useRef(null); | |||
| const queryStringHook = useQueryString(); | |||
| const queryStringHook = useQueryString(props.myOffers); | |||
| const userId = useSelector(selectUserId); | |||
| useEffect(() => { | |||
| dispatch(fetchChats()); | |||
| }, []); | |||
| useEffect(() => { | |||
| let queryObject = queryStringHook.getQueryObject(); | |||
| if (queryObject.page && queryObject.page !== 1) { | |||
| @@ -32,7 +59,7 @@ const Offers = (props) => { | |||
| }, [history.location.search]); | |||
| useEffect(() => { | |||
| if (history?.location?.state?.logo) { | |||
| if (history?.location?.state?.logo || history?.location?.state?.refetch) { | |||
| dispatch(fetchOffers({ queryString: "" })); | |||
| setPage(1); | |||
| history.location.state = undefined; | |||
| @@ -41,22 +68,7 @@ const Offers = (props) => { | |||
| useEffect(() => { | |||
| if (queryStringHook.loadedFromURL) { | |||
| dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString })); | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| search: queryStringHook.getGlobalQueryString(), | |||
| }); | |||
| window.scrollTo({ | |||
| top: 0, | |||
| behavior: "smooth", | |||
| }); | |||
| const queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (queryObject.has("page")) { | |||
| if (queryObject.get("page") !== page.toString()) | |||
| setPage(parseInt(queryObject.get("page"))); | |||
| } else { | |||
| setPage(1); | |||
| } | |||
| refetch(); | |||
| } else { | |||
| queryStringHook.appendMultipleToQueryString([ | |||
| { key: "size", value: "10" }, | |||
| @@ -70,16 +82,152 @@ const Offers = (props) => { | |||
| if (queryObject.has("page")) { | |||
| if (queryObject.get("page") !== page.toString()) { | |||
| queryStringHook.appendToQueryString("page", page); | |||
| } else { | |||
| refetch(); | |||
| } | |||
| } else { | |||
| queryStringHook.appendToQueryString("page", page); | |||
| } | |||
| }, [page]); | |||
| const pinnedOffersToShow = useMemo(() => { | |||
| if (props.myOffers) { | |||
| return mineOffers.filter((item) => item.pinned === true); | |||
| } | |||
| return pinnedOffers; | |||
| }, [pinnedOffers, mineOffers, page, props.myOffers]); | |||
| const offersToShow = useMemo(() => { | |||
| if (props.myOffers) { | |||
| return mineOffers.filter((item) => item.pinned === false); | |||
| } | |||
| return offers; | |||
| }, [offers, mineOffers, page, props.myOffers]); | |||
| const allOffersToShow = useMemo(() => { | |||
| let newOffers = [...pinnedOffersToShow, ...offersToShow]; | |||
| if (props.myOffers) { | |||
| if (selectedCategory && selectedCategory?._id !== 0) { | |||
| newOffers = newOffers.filter( | |||
| (item) => item.category.name === selectedCategory.name | |||
| ); | |||
| } | |||
| if (selectedSubcategory && selectedSubcategory?._id !== 0) { | |||
| newOffers = newOffers.filter( | |||
| (item) => item.subcategory === selectedSubcategory.name | |||
| ); | |||
| } | |||
| if (selectedLocations && selectedLocations?.length > 0) { | |||
| newOffers = newOffers.filter((item) => { | |||
| let isInOneOfLocations = false; | |||
| selectedLocations?.forEach((location) => { | |||
| if (item.location.city === location.city) { | |||
| isInOneOfLocations = true; | |||
| } | |||
| }); | |||
| return isInOneOfLocations; | |||
| }); | |||
| } | |||
| let oldOffers = [...offersToShow]; | |||
| let oldPinnedOffers = [...pinnedOffersToShow]; | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.NEW.value | |||
| ) { | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemB._created) - new Date(itemA._created) | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemB._created) - new Date(itemA._created) | |||
| ), | |||
| ]; | |||
| } | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.OLD.value | |||
| ) { | |||
| newOffers = newOffers.sort( | |||
| (itemA, itemB) => new Date(itemA._created) - new Date(itemB._created) | |||
| ); | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemA._created) - new Date(itemB._created) | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => | |||
| new Date(itemA._created) - new Date(itemB._created) | |||
| ), | |||
| ]; | |||
| } | |||
| if ( | |||
| selectedSortOption && | |||
| selectedSortOption.value === sortEnum.POPULAR.value | |||
| ) { | |||
| newOffers = [ | |||
| ...oldPinnedOffers.sort( | |||
| (itemA, itemB) => itemB.views.count - itemA.views.count | |||
| ), | |||
| ...oldOffers.sort( | |||
| (itemA, itemB) => itemB.views.count - itemA.views.count | |||
| ), | |||
| ]; | |||
| } | |||
| newOffers = newOffers.slice((page - 1) * 10, page * 10); | |||
| } | |||
| return newOffers; | |||
| }, [pinnedOffersToShow, offersToShow, props.myOffers, page]); | |||
| const totalOffers = useMemo(() => { | |||
| if (props.myOffers) { | |||
| return mineOffers?.length; | |||
| } | |||
| return total; | |||
| }, [mineOffers, total]); | |||
| const handleDifferentPage = (pageNum) => { | |||
| setPage(pageNum); | |||
| }; | |||
| const refetch = () => { | |||
| if (!props.myOffers) { | |||
| dispatch(fetchOffers({ queryString: "?" + queryStringHook.queryString })); | |||
| history.replace({ | |||
| pathname: HOME_PAGE, | |||
| search: queryStringHook.getGlobalQueryString(), | |||
| }); | |||
| } else { | |||
| dispatch(fetchMineOffers()); | |||
| } | |||
| window.scrollTo({ | |||
| top: 0, | |||
| behavior: "smooth", | |||
| }); | |||
| const queryObject = new URLSearchParams(queryStringHook.queryString); | |||
| if (queryObject.has("page")) { | |||
| if (queryObject.get("page") !== page.toString()) | |||
| setPage(parseInt(queryObject.get("page"))); | |||
| } else { | |||
| setPage(1); | |||
| } | |||
| }; | |||
| const messageOneUser = (offer) => { | |||
| const chatItem = chats.find((item) => item.chat.offerId === offer?._id); | |||
| if (chatItem !== undefined) { | |||
| history.push(`/messages/${chatItem.chat._id}`); | |||
| } else { | |||
| if (offer?.userId !== userId) { | |||
| history.push(`/messages/newMessage`, { | |||
| offerId: offer?._id, | |||
| }); | |||
| } | |||
| } | |||
| }; | |||
| return ( | |||
| <> | |||
| {offers.length === 0 ? ( | |||
| @@ -87,13 +235,14 @@ const Offers = (props) => { | |||
| ) : ( | |||
| <OffersContainer ref={offersRef}> | |||
| <> | |||
| {pinnedOffers != undefined && | |||
| pinnedOffers.map((item) => { | |||
| {allOffersToShow != undefined && | |||
| allOffersToShow.map((item) => { | |||
| return ( | |||
| <OfferCard | |||
| key={item._id} | |||
| offer={item} | |||
| halfwidth={props.isGrid} | |||
| messageUser={messageOneUser} | |||
| /> | |||
| ); | |||
| })} | |||
| @@ -109,7 +258,7 @@ const Offers = (props) => { | |||
| ); | |||
| })} | |||
| <Paging | |||
| totalElements={total} | |||
| totalElements={totalOffers} | |||
| elementsPerPage={10} | |||
| current={page} | |||
| changePage={handleDifferentPage} | |||
| @@ -123,6 +272,11 @@ const Offers = (props) => { | |||
| Offers.propTypes = { | |||
| children: PropTypes.node, | |||
| isGrid: PropTypes.bool, | |||
| myOffers: PropTypes.bool, | |||
| }; | |||
| Offers.defaultProps = { | |||
| myOffers: false, | |||
| }; | |||
| export default Offers; | |||
| @@ -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; | |||
| @@ -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")} | |||
| @@ -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} | |||
| /> | |||
| ); | |||
| }; | |||
| @@ -29,7 +29,7 @@ export const MyProfile = () => { | |||
| setProfileAsArray([ | |||
| { | |||
| alt: "Profile", | |||
| src: `${profile.image}`, | |||
| src: profile.image, | |||
| title: profile.company.name, | |||
| onClick: () => seeMyProfile(), | |||
| text: ( | |||
| @@ -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> | |||
| @@ -22,9 +22,7 @@ import { | |||
| MessageIcon, | |||
| MessageButton, | |||
| } from "./ProfileCard.styled"; | |||
| import { Grid, Stack } from "@mui/material"; | |||
| import PersonOutlineIcon from "@mui/icons-material/PersonOutline"; | |||
| import { useRouteMatch } from "react-router-dom"; | |||
| import { fetchProfile } from "../../store/actions/profile/profileActions"; | |||
| @@ -35,6 +33,7 @@ import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { useState } from "react"; | |||
| import { fetchProfileOffers } from "../../store/actions/offers/offersActions"; | |||
| import EditProfile from "./EditProfile"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const ProfileCard = () => { | |||
| const [isMyProfile, setIsMyProfile] = useState(false); | |||
| @@ -44,7 +43,8 @@ const ProfileCard = () => { | |||
| const profile = useSelector(selectProfile); | |||
| const userId = useSelector(selectUserId); | |||
| const idProfile = routeMatch.params.idProfile; | |||
| console.log(idProfile); | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| if (idProfile?.length > 0) { | |||
| reFetchProfile(); | |||
| @@ -78,7 +78,6 @@ const ProfileCard = () => { | |||
| document.body.style.overflow = "auto"; | |||
| } | |||
| console.log(profile); | |||
| return ( | |||
| <> | |||
| <ProfileCardContainer> | |||
| @@ -90,7 +89,7 @@ const ProfileCard = () => { | |||
| sx={{ mb: 1.4 }} | |||
| > | |||
| <PersonOutlineIcon color="action" sx={{ mr: 0.9 }} /> | |||
| <HeaderTitle>Moj Profil</HeaderTitle> | |||
| <HeaderTitle>{t("profile.myProfile")}</HeaderTitle> | |||
| </Grid> | |||
| <ProfileCardWrapper variant="outlined" isMyProfile={isMyProfile}> | |||
| {isMyProfile ? ( | |||
| @@ -139,7 +138,7 @@ const ProfileCard = () => { | |||
| > | |||
| <PocketIcon /> | |||
| <ProfilePIB isMyProfile={isMyProfile} variant="subtitle2"> | |||
| PIB: {profile?.company?.PIB} | |||
| {t("profile.PIB")} {profile?.company?.PIB} | |||
| </ProfilePIB> | |||
| </ProfilePIBContainer> | |||
| </Grid> | |||
| @@ -185,11 +184,13 @@ const ProfileCard = () => { | |||
| sx={{ width: "fit-content" }} | |||
| > | |||
| <StatsItem variant="subtitle2"> | |||
| <b>{profile?.statistics?.publishes?.count}</b> objava | |||
| <b>{profile?.statistics?.publishes?.count}</b> | |||
| {t("profile.publishes")} | |||
| </StatsItem> | |||
| <StatsItem variant="subtitle2"> | |||
| <b>{percentOfSucceededExchanges}%</b> uspešna komunikacija | |||
| <b>{percentOfSucceededExchanges}%</b> | |||
| {t("profile.successComunication")} | |||
| </StatsItem> | |||
| </Grid> | |||
| <Grid | |||
| @@ -200,10 +201,12 @@ const ProfileCard = () => { | |||
| sx={{ width: "fit-content" }} | |||
| > | |||
| <StatsItem variant="subtitle2"> | |||
| <b>{profile?.statistics?.views?.count}</b> ukupnih pregleda | |||
| <b>{profile?.statistics?.views?.count}</b> | |||
| {t("profile.numberOfViews")} | |||
| </StatsItem> | |||
| <StatsItem variant="subtitle2"> | |||
| <b>{percentOfSucceededExchanges}%</b> korektna saradnja | |||
| <b>{percentOfSucceededExchanges}%</b> | |||
| {t("profile.successCooperation")} | |||
| </StatsItem> | |||
| </Grid> | |||
| </ProfileStats> | |||
| @@ -12,7 +12,7 @@ const PrivateRoute = ({ ...props }) => { | |||
| const isUserAuthenticated = useMemo(() => { | |||
| if (userId?.length === 0) return false; | |||
| return true; | |||
| }) | |||
| }, [userId]) | |||
| useEffect(() => { | |||
| if (!isUserAuthenticated) { | |||
| @@ -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; | |||
| @@ -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; | |||
| ` | |||
| @@ -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%; | |||
| @@ -0,0 +1,94 @@ | |||
| import React, { useEffect, useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ReviewList, | |||
| ReviewsBox, | |||
| ReviewsHeader, | |||
| ReviewsTitle, | |||
| } from "./UserReviews.styled"; | |||
| import StarBorderIcon from "@mui/icons-material/StarBorder"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import UserReviewsCard from "../Cards/UserReviewsCard/UserReviewsCard"; | |||
| import NoReviews from "./NoReviews/NoReviews"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectOffer } from "../../store/selectors/offersSelectors"; | |||
| import { selectSelectedReviews } from "../../store/selectors/reviewSelector"; | |||
| import { useRouteMatch } from "react-router-dom"; | |||
| import { fetchReviews } from "../../store/actions/review/reviewActions"; | |||
| const UserReviews = (props) => { | |||
| const { t } = useTranslation(); | |||
| const offer = useSelector(selectOffer); | |||
| const reviews = useSelector(selectSelectedReviews); | |||
| const routeMatch = useRouteMatch(); | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| console.log(routeMatch) | |||
| if (props.profileReviews && routeMatch.params?.idProfile) { | |||
| let idProfile = routeMatch.params.idProfile; | |||
| dispatch(fetchReviews(idProfile)); | |||
| } | |||
| }, [props.profileReviews, routeMatch]) | |||
| const lastThreeReviews = useMemo(() => { | |||
| if (props.givingReview) { | |||
| return [...props.profileReviews]; | |||
| } | |||
| if (props.isProfileReviews) { | |||
| return [...reviews.slice(0, 3)]; | |||
| } | |||
| if (offer?.companyData?.lastThreeReviews) { | |||
| return [...offer?.companyData.lastThreeReviews]; | |||
| } | |||
| return []; | |||
| }, [props.profileReviews, offer, props.isProfileReviews, reviews]); | |||
| return ( | |||
| <ReviewsBox | |||
| className={props.className} | |||
| numOfReviews={lastThreeReviews?.length} | |||
| > | |||
| {!props.givingReview && ( | |||
| <ReviewsHeader | |||
| container | |||
| direction="row" | |||
| justifyContent="start" | |||
| alignItems="center" | |||
| sx={{ mb: 1.4 }} | |||
| > | |||
| <StarBorderIcon color="action" sx={{ mr: 0.9 }} /> | |||
| <ReviewsTitle>{t("reviews.rates")}</ReviewsTitle> | |||
| </ReviewsHeader> | |||
| )} | |||
| <ReviewList> | |||
| {lastThreeReviews?.length > 0 ? ( | |||
| lastThreeReviews?.map((review, index) => ( | |||
| <UserReviewsCard | |||
| review={review} | |||
| key={index} | |||
| givingReview={props.givingReview} | |||
| /> | |||
| )) | |||
| ) : ( | |||
| <NoReviews></NoReviews> | |||
| )} | |||
| </ReviewList> | |||
| </ReviewsBox> | |||
| ); | |||
| }; | |||
| UserReviews.propTypes = { | |||
| children: PropTypes.node, | |||
| heading: PropTypes.string, | |||
| isProfileReviews: PropTypes.bool, | |||
| profileReviews: PropTypes.any, | |||
| className: PropTypes.string, | |||
| givingReview: PropTypes.bool, | |||
| }; | |||
| UserReviews.defaultProps = { | |||
| isProfileReviews: false, | |||
| profileReviews: [], | |||
| }; | |||
| export default UserReviews; | |||
| @@ -0,0 +1,131 @@ | |||
| import styled from "styled-components"; | |||
| import { List, Box, Typography, Grid } from "@mui/material"; | |||
| import ThumbUpIcon from "@mui/icons-material/ThumbUp"; | |||
| import ThumbDownIcon from "@mui/icons-material/ThumbDown"; | |||
| import selectedTheme from "../../themes"; | |||
| export const ReviewsBox = styled(Box)` | |||
| width: 100%; | |||
| /* One review is 185px in height and 82 px are header title + padding */ | |||
| /* height: ${props => props.numOfReviews > 0 ? props.numOfReviews * 185 + 82 + 'px' : `calc(100% - 90px)`}; */ | |||
| /* max-height: 100vh; */ | |||
| @media (max-width: 1200px) { | |||
| padding: 0; | |||
| } | |||
| @media (max-width: 600px) { | |||
| position: relative; | |||
| top: -45px; | |||
| overflow: hidden; | |||
| max-height: ${props => props.numOfReviews > 2 ? "650px" : props.numOfReviews > 1 ? "450px" : "350px"}; | |||
| padding: 0; | |||
| } | |||
| `; | |||
| export const ReviewsHeader = styled(Grid)` | |||
| @media (max-width: 800px) { | |||
| display: none; | |||
| } | |||
| ` | |||
| export const ReviewsTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 16px; | |||
| @media (max-width: 800px) { | |||
| display: none; | |||
| } | |||
| ` | |||
| export const ReviewList = styled(List)` | |||
| background: white; | |||
| padding: 2rem; | |||
| border-radius: 4px 0 0 4px; | |||
| padding-right: .4rem; | |||
| height: 100%; | |||
| width: 100%; | |||
| border: 1px solid ${selectedTheme.borderNormal}; | |||
| /* overflow-y: auto; */ | |||
| &::-webkit-scrollbar { | |||
| width: 5px; | |||
| } | |||
| &::-webkit-scrollbar-track { | |||
| background: #ddd; | |||
| } | |||
| &::-webkit-scrollbar-thumb { | |||
| background: #777; | |||
| } | |||
| scrollbar-width: thin; | |||
| scrollbar-color: #ddd; | |||
| `; | |||
| export const ThumbUp = styled(ThumbUpIcon)` | |||
| position: relative; | |||
| left: -8px; | |||
| ` | |||
| export const ThumbDown = styled(ThumbDownIcon)` | |||
| position: relative; | |||
| left: -8px; | |||
| ` | |||
| export const NoReviewsContainer = styled(Box)` | |||
| ` | |||
| export const NoReviewsText = styled(Typography)` | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-size: 24px; | |||
| font-family: "Open Sans"; | |||
| text-align: center; | |||
| font-weight: 700; | |||
| width: 100%; | |||
| ` | |||
| export const NoReviewsAltText = styled(Typography)` | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| font-family: "Open Sans"; | |||
| text-align: center; | |||
| width: 100%; | |||
| margin-bottom: 36px; | |||
| ` | |||
| export const ProfileImage = styled.img` | |||
| width: 54px; | |||
| height: 54px; | |||
| border-radius: 100%; | |||
| ` | |||
| export const ProfileImageContainer = styled(Box)` | |||
| width: 54px; | |||
| height: 54px; | |||
| border-radius: 100%; | |||
| margin-right: 14px; | |||
| ` | |||
| export const ReviewQuote = styled(Grid)` | |||
| position: relative; | |||
| left: 8px; | |||
| ` | |||
| export const ThumbBox = styled(Grid)` | |||
| ` | |||
| export const ReviewQuoteBox = styled(Grid)` | |||
| ` | |||
| export const ReviewQuoteText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| ` | |||
| export const ReviewDetails = styled(Grid)` | |||
| ` | |||
| export const ReviewDetailsText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| font-style: italic; | |||
| letter-spacing: 0.02em; | |||
| ` | |||
| export const ReviewDetailsValue = styled(Typography)` | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-style: normal; | |||
| font-weight: 600; | |||
| ` | |||
| export const ProfileName = styled(Typography)` | |||
| font-weight: 600; | |||
| font-size: 16px; | |||
| font-family: "Open Sans"; | |||
| ` | |||
| export const ReviewContainer = styled(Box)` | |||
| ` | |||
| @@ -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" | |||
| > | |||
| "{review?.quote}" | |||
| </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; | |||
| @@ -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" | |||
| @@ -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" | |||
| } | |||
| } | |||
| @@ -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]); | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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; | |||
| @@ -22,6 +22,7 @@ export default { | |||
| labelPhone: "Telefon", | |||
| labelLocation: "Lokacija", | |||
| labelWebsite: "Adresa Websajta", | |||
| logout: "Odjavi se", | |||
| next: "Sledeće", | |||
| nextPage: "Sledeća strana", | |||
| previousPage: "Prethodna strana", | |||
| @@ -157,6 +158,7 @@ export default { | |||
| supportedImagesFormats: | |||
| "Podržani formati fotografija: <strong>.JPG</strong> | <strong>.JPEG</strong> | <strong>.PNG</strong>", | |||
| continue: "NASTAVI", | |||
| publish: "OBJAVI", | |||
| }, | |||
| apiErrors: { | |||
| somethingWentWrong: "Greska sa serverom!", | |||
| @@ -170,10 +172,26 @@ export default { | |||
| checkEverything: "POGLEDAJ SVE", | |||
| myMessages: "Moje poruke", | |||
| newOffers: "Najnovije ponude", | |||
| navMenu: "Navigacioni Meni", | |||
| }, | |||
| reviews: { | |||
| title: "Ova kompanija još uvek nema ocenu.", | |||
| altTitle: "Budite prvi da je ocenite", | |||
| modalTitle: "Ocenjivanje kompanije", | |||
| isCorrectCommunication: "Korektna komunikacija", | |||
| hasExchangeSucceed: "Uspešna trampa", | |||
| comment: "Komentar", | |||
| commentError: "Komentar mora imati minimum 5 karaktera!", | |||
| selectFieldError: "Odaberite jedno polje!", | |||
| leaveComment: "Ostavi komentar", | |||
| rates: "Ocene", | |||
| }, | |||
| messages: { | |||
| headerTitle: "Moje Ćaskanje", | |||
| cardProduct: "Proizvod: ", | |||
| miniChatHeaderTitle: "Moje Poruke", | |||
| send: "Pošalji", | |||
| sendPlaceholder: "Poruka...", | |||
| }, | |||
| editProfile: { | |||
| website: "Web Sajt*", | |||
| @@ -204,4 +222,12 @@ export default { | |||
| "Nažalost ne postoji ni jedna objava <br /> za unete kriterijume.", | |||
| showAllOffers: "Pogledaj sve objave", | |||
| }, | |||
| profile: { | |||
| myProfile: "Moj profil", | |||
| PIB: "PIB:", | |||
| publishes: "objava", | |||
| successComunication: "uspešna komunikacija", | |||
| numberOfViews: "ukupnih pregleda", | |||
| successCooperation: "korektna saradnja", | |||
| }, | |||
| }; | |||
| @@ -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; | |||
| @@ -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)` | |||
| ` | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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 />} /> | |||
| ) | |||
| } | |||
| @@ -11,5 +11,4 @@ export const ChatMessagesPageContainer = styled(Container)` | |||
| max-width: none; | |||
| flex: 1; | |||
| display: flex; | |||
| flex-direction: column; | |||
| `; | |||
| @@ -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 }}> | |||
| @@ -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; | |||
| @@ -0,0 +1,6 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const MyOffersContainer = styled(Box)` | |||
| ` | |||