| @@ -13,6 +13,9 @@ | |||
| "react/jsx-filename-extension": "off", | |||
| "react/jsx-props-no-spreading": "off", | |||
| "react/button-has-type": "off", | |||
| "react/max-len": ["error", { | |||
| "code": 100 | |||
| }], | |||
| "react/require-default-props": "off", | |||
| "import/no-extraneous-dependencies": "off", | |||
| "import/prefer-default-export": "off", | |||
| @@ -1,22 +1,18 @@ | |||
| { | |||
| "env": { | |||
| "browser": true, | |||
| "es2021": true | |||
| "env": { | |||
| "browser": true, | |||
| "es2021": true | |||
| }, | |||
| "extends": ["eslint:recommended", "plugin:react/recommended"], | |||
| "parserOptions": { | |||
| "ecmaFeatures": { | |||
| "jsx": true | |||
| }, | |||
| "extends": [ | |||
| "eslint:recommended", | |||
| "plugin:react/recommended" | |||
| ], | |||
| "parserOptions": { | |||
| "ecmaFeatures": { | |||
| "jsx": true | |||
| }, | |||
| "ecmaVersion": 12, | |||
| "sourceType": "module" | |||
| }, | |||
| "plugins": [ | |||
| "react" | |||
| ], | |||
| "rules": { | |||
| } | |||
| "ecmaVersion": 12, | |||
| "sourceType": "module" | |||
| }, | |||
| "plugins": ["react"], | |||
| "rules": { | |||
| // "max-lines": ["warn", 100] | |||
| } | |||
| } | |||
| @@ -76,6 +76,7 @@ const App = () => { | |||
| <Router history={history}> | |||
| <Helmet> | |||
| <title>{i18next.t("app.title")}</title> | |||
| </Helmet> | |||
| <StyledEngineProvider injectFirst> | |||
| {/* <button onClick={handleClick}>Kik</button> */} | |||
| @@ -40,7 +40,7 @@ import MyOffers from "./pages/MyOffers/MyOffers"; | |||
| const AppRoutes = () => { | |||
| return ( | |||
| <Switch> | |||
| <Route exact path={BASE_PAGE} component={LoginPage} /> | |||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||
| <Route exact path={LOGIN_PAGE} component={LoginPage} /> | |||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | |||
| <Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| @@ -1,41 +1,25 @@ | |||
| import React, { useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CheckButton, | |||
| OfferImage, | |||
| OfferTitle, | |||
| ChatOffer, | |||
| Commands, | |||
| ChatInfo, | |||
| OfferText, | |||
| ChatCardContainer, | |||
| Col, | |||
| UserImage, | |||
| OfferCardContainer, | |||
| UserImgWrapper, | |||
| OfferImgWrapper, | |||
| UserName, | |||
| LastMessage, | |||
| Line, | |||
| LocationContainer, | |||
| XSText, | |||
| LocationIcon, | |||
| OfferCardContainerMobile, | |||
| OfferTextMobile, | |||
| OfferTitleMobile, | |||
| PhoneIconContainer, | |||
| PhoneIcon, | |||
| LocationIconContainer, | |||
| } from "./ChatCard.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import useScreenDimensions from "../../../hooks/useScreenDimensions"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import LittleOfferDetails from "./LittleOfferDetails/LittleOfferDetails"; | |||
| import MobileOfferDetails from "./MobileOfferDetails/MobileOfferDetails"; | |||
| import OfferLocation from "./OfferLocation/OfferLocation"; | |||
| import ChatCommands from "./ChatCommands/ChatCommands"; | |||
| const ChatCard = (props) => { | |||
| const history = useHistory(); | |||
| const dimensions = useScreenDimensions(); | |||
| const { t } = useTranslation(); | |||
| const chat = useMemo(() => { | |||
| return props.chat; | |||
| @@ -47,7 +31,6 @@ const ChatCard = (props) => { | |||
| } | |||
| return ""; | |||
| }, [chat]); | |||
| const routeToItem = () => { | |||
| history.push(`/messages/${chat?.chat?._id}`); | |||
| }; | |||
| @@ -66,51 +49,20 @@ const ChatCard = (props) => { | |||
| <UserName>{chat?.interlocutorData?.name}</UserName> | |||
| {/* Only shows on Mobile */} | |||
| <OfferCardContainerMobile> | |||
| <OfferTextMobile>{t("messages.cardProduct")}</OfferTextMobile> | |||
| <OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile> | |||
| </OfferCardContainerMobile> | |||
| <MobileOfferDetails chat={chat} /> | |||
| {/* ^^^^^ */} | |||
| <LastMessage>{lastMessage}</LastMessage> | |||
| <LocationContainer> | |||
| <LocationIconContainer> | |||
| <LocationIcon /> | |||
| </LocationIconContainer> | |||
| <XSText>{chat?.interlocutorData?.location}</XSText> | |||
| </LocationContainer> | |||
| <OfferLocation chat={chat} /> | |||
| </ChatInfo> | |||
| </Col> | |||
| <Line /> | |||
| {/* Only shows on Desktop */} | |||
| <Col mobileDisappear> | |||
| <ChatOffer> | |||
| <OfferImgWrapper> | |||
| <OfferImage src={chat?.offerData?.firstImage} /> | |||
| </OfferImgWrapper> | |||
| <OfferCardContainer> | |||
| <OfferText>{t("messages.cardProduct")}</OfferText> | |||
| <OfferTitle>{chat?.offerData?.name}</OfferTitle> | |||
| </OfferCardContainer> | |||
| </ChatOffer> | |||
| </Col> | |||
| <LittleOfferDetails chat={chat} /> | |||
| {/* ^^^^^^^ */} | |||
| <Commands> | |||
| <PhoneIconContainer> | |||
| <PhoneIcon /> | |||
| </PhoneIconContainer> | |||
| <CheckButton | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor={selectedTheme.primaryPurple} | |||
| variant={"outlined"} | |||
| style={{ fontWeight: "600" }} | |||
| onClick={routeToItem} | |||
| > | |||
| {t("messages.seeChats")} | |||
| </CheckButton> | |||
| </Commands> | |||
| <ChatCommands routeToItem={() => routeToItem(chat?.chat?._id)} /> | |||
| </ChatCardContainer> | |||
| ); | |||
| }; | |||
| @@ -1,10 +1,6 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { ReactComponent as Phone } from "../../../assets/images/svg/phone.svg"; | |||
| import { ReactComponent as Location } from "../../../assets/images/svg/location.svg"; | |||
| export const ChatCardContainer = styled(Container)` | |||
| display: flex; | |||
| @@ -14,13 +10,10 @@ export const ChatCardContainer = styled(Container)` | |||
| box-sizing: border-box; | |||
| margin: 10px 0; | |||
| background-color: ${(props) => | |||
| props.sponsored === "true" | |||
| ? selectedTheme.backgroundSponsoredColor | |||
| : "white"}; | |||
| props.sponsored === "true" ? selectedTheme.backgroundSponsoredColor : "white"}; | |||
| border-radius: 4px; | |||
| ${(props) => | |||
| props.sponsored === "true" && | |||
| `border: 1px solid ${selectedTheme.borderSponsoredColor};`} | |||
| props.sponsored === "true" && `border: 1px solid ${selectedTheme.borderSponsoredColor};`} | |||
| padding: 16px; | |||
| max-width: 2000px; | |||
| height: 180px; | |||
| @@ -31,11 +24,9 @@ export const ChatCardContainer = styled(Container)` | |||
| margin: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| height: 330px; | |||
| `height: 330px; | |||
| width: 180px; | |||
| margin: 0 18px; | |||
| `} | |||
| margin: 0 18px;`} | |||
| } | |||
| `; | |||
| export const UserImage = styled.img` | |||
| @@ -47,7 +38,6 @@ export const UserImage = styled.img` | |||
| height: 72px; | |||
| } | |||
| `; | |||
| export const UserImgWrapper = styled(Box)` | |||
| overflow: hidden; | |||
| border-radius: 50%; | |||
| @@ -59,191 +49,22 @@ export const UserImgWrapper = styled(Box)` | |||
| min-width: 80px; | |||
| } | |||
| `; | |||
| export const OfferImgWrapper = styled(Box)` | |||
| overflow: hidden; | |||
| border-radius: 4px; | |||
| width: 72px; | |||
| height: 72px; | |||
| min-width: 72px; | |||
| max-width: 72px; | |||
| `; | |||
| export const LocationIcon = styled(Location)` | |||
| height: 12px; | |||
| width: 12px; | |||
| `; | |||
| export const OfferCardContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| box-sizing: border-box; | |||
| padding: 16px; | |||
| max-width: 2000px; | |||
| position: relative; | |||
| @media (max-width: 550px) { | |||
| } | |||
| `; | |||
| export const OfferCardContainerMobile = styled(Box)` | |||
| display: none; | |||
| @media (max-width: 550px) { | |||
| position: relative; | |||
| display: flex; | |||
| flex-direction: column; | |||
| box-sizing: border-box; | |||
| } | |||
| `; | |||
| export const OfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 18px; | |||
| cursor: pointer; | |||
| @media (max-width: 550px) { | |||
| font-size: 14px; | |||
| display: none; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: flex; | |||
| flex: none; | |||
| position: relative; | |||
| line-height: 22px; | |||
| margin-top: 5px; | |||
| font-size: 18px; | |||
| `} | |||
| } | |||
| `; | |||
| export const OfferTitleMobile = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| display: none; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 12px; | |||
| cursor: pointer; | |||
| @media (max-width: 550px) { | |||
| display: block; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: flex; | |||
| flex: none; | |||
| position: relative; | |||
| line-height: 22px; | |||
| margin-top: 5px; | |||
| font-size: 18px; | |||
| `} | |||
| } | |||
| `; | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 180px; | |||
| height: 48px; | |||
| &: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 PhoneIconContainer = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| @media (max-width: 600px) { | |||
| width: 32px; | |||
| height: 32px; | |||
| top: 16px; | |||
| right: 16px; | |||
| padding: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: none; | |||
| `} | |||
| & button svg { | |||
| width: 16px; | |||
| height: 16px; | |||
| position: relative; | |||
| top: -3px; | |||
| left: -2.4px; | |||
| } | |||
| } | |||
| `; | |||
| export const ChatOffer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| padding-left: 36px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const OfferText = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: "12px"; | |||
| color: ${selectedTheme.primaryText}; | |||
| `; | |||
| export const OfferTextMobile = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: 9px; | |||
| color: ${selectedTheme.primaryText}; | |||
| `; | |||
| export const Commands = styled(Box)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| align-items: flex-end; | |||
| @media (max-width: 600px) { | |||
| align-items: flex-start; | |||
| } | |||
| `; | |||
| export const ChatInfo = styled(Box)` | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| `; | |||
| export const Col = styled(Box)` | |||
| display: flex; | |||
| 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;`} | |||
| ${props => props.mobileDisappear && 'display: none;'} | |||
| } | |||
| `; | |||
| export const UserName = styled(Typography)` | |||
| margin-bottom: 12px; | |||
| font-family: "Open Sans"; | |||
| @@ -255,7 +76,6 @@ export const UserName = styled(Typography)` | |||
| font-size: 18px; | |||
| } | |||
| `; | |||
| export const LastMessage = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryDarkTextThird}; | |||
| @@ -273,39 +93,6 @@ export const LastMessage = styled(Typography)` | |||
| display: none; | |||
| } | |||
| `; | |||
| export const LocationContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| gap: 2px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const LocationIconContainer = styled(Box)` | |||
| height: 12px; | |||
| width: auto; | |||
| position: relative; | |||
| `; | |||
| export const XSText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 14px; | |||
| position: relative; | |||
| `; | |||
| export const OfferImage = styled.img` | |||
| max-width: 72px; | |||
| max-height: 72px; | |||
| min-width: 72px; | |||
| width: 72px; | |||
| height: 72px; | |||
| border-radius: 4px; | |||
| `; | |||
| export const Line = styled(Box)` | |||
| border-left: 1px solid rgba(0, 0, 0, 0.15); | |||
| height: 100px; | |||
| @@ -313,13 +100,4 @@ export const Line = styled(Box)` | |||
| margin: auto 0; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const PhoneIcon = styled(Phone)` | |||
| @media (max-width: 600px) { | |||
| width: 14px; | |||
| height: 14px; | |||
| position: relative; | |||
| top: 1px; | |||
| } | |||
| `; | |||
| }`; | |||
| @@ -0,0 +1,37 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CheckButton, | |||
| Commands, | |||
| PhoneIcon, | |||
| PhoneIconContainer, | |||
| } from "./ChatCommands.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const ChatCommands = (props) => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Commands> | |||
| <PhoneIconContainer> | |||
| <PhoneIcon /> | |||
| </PhoneIconContainer> | |||
| <CheckButton | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| textcolor={selectedTheme.primaryPurple} | |||
| variant={"outlined"} | |||
| style={{ fontWeight: "600" }} | |||
| onClick={props.routeToItem} | |||
| > | |||
| {t("messages.seeChats")} | |||
| </CheckButton> | |||
| </Commands> | |||
| ); | |||
| }; | |||
| ChatCommands.propTypes = { | |||
| routeToItem: PropTypes.func, | |||
| }; | |||
| export default ChatCommands; | |||
| @@ -0,0 +1,75 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import { ReactComponent as Phone } from "../../../../assets/images/svg/phone.svg"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import IconButton from "../../../IconButton/IconButton"; | |||
| export const Commands = styled(Box)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: space-between; | |||
| align-items: flex-end; | |||
| @media (max-width: 600px) { | |||
| align-items: flex-start; | |||
| } | |||
| `; | |||
| export const PhoneIcon = styled(Phone)` | |||
| @media (max-width: 600px) { | |||
| width: 14px; | |||
| height: 14px; | |||
| position: relative; | |||
| top: 1px; | |||
| } | |||
| `; | |||
| export const PhoneIconContainer = styled(IconButton)` | |||
| width: 40px; | |||
| height: 40px; | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| border-radius: 100%; | |||
| padding-top: 2px; | |||
| text-align: center; | |||
| @media (max-width: 600px) { | |||
| width: 32px; | |||
| height: 32px; | |||
| top: 16px; | |||
| right: 16px; | |||
| padding: 0; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: none; | |||
| `} | |||
| & button svg { | |||
| width: 16px; | |||
| height: 16px; | |||
| position: relative; | |||
| top: -3px; | |||
| left: -2.4px; | |||
| } | |||
| } | |||
| `; | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 180px; | |||
| height: 48px; | |||
| &: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; | |||
| `; | |||
| @@ -0,0 +1,36 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ChatOffer, | |||
| OfferCardContainer, | |||
| OfferImage, | |||
| OfferImgWrapper, | |||
| OfferText, | |||
| OfferTitle, | |||
| } from "./LittleOfferDetails.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Col } from "../ChatCard.styled"; | |||
| const LittleOfferDetails = (props) => { | |||
| const chat = props.chat; | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Col mobileDisappear> | |||
| <ChatOffer> | |||
| <OfferImgWrapper> | |||
| <OfferImage src={chat?.offerData?.firstImage} /> | |||
| </OfferImgWrapper> | |||
| <OfferCardContainer> | |||
| <OfferText>{t("messages.cardProduct")}</OfferText> | |||
| <OfferTitle>{chat?.offerData?.name}</OfferTitle> | |||
| </OfferCardContainer> | |||
| </ChatOffer> | |||
| </Col> | |||
| ); | |||
| }; | |||
| LittleOfferDetails.propTypes = { | |||
| chat: PropTypes.any, | |||
| }; | |||
| export default LittleOfferDetails; | |||
| @@ -0,0 +1,85 @@ | |||
| import { Box, Container, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const ChatOffer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| padding-left: 36px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const OfferText = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: "12px"; | |||
| color: ${selectedTheme.primaryText}; | |||
| `; | |||
| export const OfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 18px; | |||
| cursor: pointer; | |||
| @media (max-width: 550px) { | |||
| font-size: 14px; | |||
| display: none; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: flex; | |||
| flex: none; | |||
| position: relative; | |||
| line-height: 22px; | |||
| margin-top: 5px; | |||
| font-size: 18px; | |||
| `} | |||
| } | |||
| `; | |||
| export const OfferCardContainer = styled(Container)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| box-sizing: border-box; | |||
| padding: 16px; | |||
| max-width: 2000px; | |||
| position: relative; | |||
| @media (max-width: 550px) { | |||
| } | |||
| `; | |||
| export const OfferImgWrapper = styled(Box)` | |||
| overflow: hidden; | |||
| border-radius: 4px; | |||
| width: 72px; | |||
| height: 72px; | |||
| min-width: 72px; | |||
| max-width: 72px; | |||
| `; | |||
| export const OfferImage = styled.img` | |||
| max-width: 72px; | |||
| max-height: 72px; | |||
| min-width: 72px; | |||
| width: 72px; | |||
| height: 72px; | |||
| border-radius: 4px; | |||
| `; | |||
| export const Col = styled(Box)` | |||
| display: flex; | |||
| 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;`} | |||
| } | |||
| `; | |||
| @@ -0,0 +1,25 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| OfferCardContainerMobile, | |||
| OfferTextMobile, | |||
| OfferTitleMobile, | |||
| } from "./MobileOfferDetails.styled"; | |||
| const MobileOfferDetails = (props) => { | |||
| const chat = props.chat; | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <OfferCardContainerMobile> | |||
| <OfferTextMobile>{t("messages.cardProduct")}</OfferTextMobile> | |||
| <OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile> | |||
| </OfferCardContainerMobile> | |||
| ); | |||
| }; | |||
| MobileOfferDetails.propTypes = { | |||
| chat: PropTypes.any, | |||
| }; | |||
| export default MobileOfferDetails; | |||
| @@ -0,0 +1,43 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const OfferCardContainerMobile = styled(Box)` | |||
| display: none; | |||
| @media (max-width: 550px) { | |||
| position: relative; | |||
| display: flex; | |||
| flex-direction: column; | |||
| box-sizing: border-box; | |||
| } | |||
| `; | |||
| export const OfferTitleMobile = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| display: none; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 12px; | |||
| cursor: pointer; | |||
| @media (max-width: 550px) { | |||
| display: block; | |||
| ${(props) => | |||
| props.vertical && | |||
| ` | |||
| display: flex; | |||
| flex: none; | |||
| position: relative; | |||
| line-height: 22px; | |||
| margin-top: 5px; | |||
| font-size: 18px; | |||
| `} | |||
| } | |||
| `; | |||
| export const OfferTextMobile = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: 9px; | |||
| color: ${selectedTheme.primaryText}; | |||
| `; | |||
| @@ -0,0 +1,21 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { LocationContainer, LocationIcon, LocationIconContainer, XSText } from './OfferLocation.styled'; | |||
| const OfferLocation = (props) => { | |||
| const chat = props.chat; | |||
| return ( | |||
| <LocationContainer> | |||
| <LocationIconContainer> | |||
| <LocationIcon /> | |||
| </LocationIconContainer> | |||
| <XSText>{chat?.interlocutorData?.location}</XSText> | |||
| </LocationContainer> | |||
| ) | |||
| } | |||
| OfferLocation.propTypes = { | |||
| chat: PropTypes.any, | |||
| } | |||
| export default OfferLocation | |||
| @@ -0,0 +1,33 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import { ReactComponent as Location } from "../../../../assets/images/svg/location.svg"; | |||
| export const LocationContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| gap: 2px; | |||
| @media (max-width: 600px) { | |||
| display: none; | |||
| } | |||
| `; | |||
| export const LocationIconContainer = styled(Box)` | |||
| height: 12px; | |||
| width: auto; | |||
| position: relative; | |||
| `; | |||
| export const XSText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 14px; | |||
| position: relative; | |||
| `; | |||
| export const LocationIcon = styled(Location)` | |||
| height: 12px; | |||
| width: 12px; | |||
| `; | |||
| @@ -123,19 +123,27 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => { | |||
| category, | |||
| condition, | |||
| description, | |||
| // images, | |||
| images, | |||
| location, | |||
| nameOfProduct, | |||
| subcategory, | |||
| } = informations; | |||
| if (stepNumber === 1) { | |||
| setInformations({}); | |||
| setInformations({ | |||
| category, | |||
| condition, | |||
| description, | |||
| location, | |||
| nameOfProduct, | |||
| subcategory, | |||
| }); | |||
| } | |||
| if (stepNumber === 2) { | |||
| setInformations({ | |||
| category, | |||
| condition, | |||
| description, | |||
| images, | |||
| location, | |||
| nameOfProduct, | |||
| subcategory, | |||
| @@ -177,10 +185,18 @@ const CreateOffer = ({ history, closeCreateOfferModal, editOffer, offer }) => { | |||
| functions={[() => goStepBack(1), () => goStepBack(2)]} | |||
| /> | |||
| {currentStep === 1 && ( | |||
| <FirstPartCreateOffer handleNext={handleNext} offer={offer} /> | |||
| <FirstPartCreateOffer | |||
| handleNext={handleNext} | |||
| offer={offer} | |||
| informations={informations} | |||
| /> | |||
| )} | |||
| {currentStep === 2 && ( | |||
| <SecondPartCreateOffer handleNext={handleNext} offer={offer} /> | |||
| <SecondPartCreateOffer | |||
| handleNext={handleNext} | |||
| offer={offer} | |||
| informations={informations} | |||
| /> | |||
| )} | |||
| {currentStep === 3 && ( | |||
| <ThirdPartCreateOffer | |||
| @@ -24,12 +24,31 @@ const FirstPartCreateOffer = (props) => { | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| if (!props.offer) { | |||
| if (Object.keys(props.informations).length !== 0) { | |||
| formik.setFieldValue("nameOfProduct", props.informations.nameOfProduct); | |||
| formik.setFieldValue("description", props.informations.description); | |||
| formik.setFieldValue("location", props.informations.location); | |||
| formik.setFieldValue("category", props.informations.category); | |||
| formik.setFieldValue("subcategory", props.informations.subcategory); | |||
| let scat = categories.filter( | |||
| (cat) => cat.name === props.informations.category | |||
| ); | |||
| setSubcat(scat[0].subcategories.map((x) => x.name)); | |||
| } | |||
| } else { | |||
| formik.setFieldValue("location", props.offer.location.city); | |||
| formik.setFieldValue("category", props.offer.category.name); | |||
| formik.setFieldValue("subcategory", props.offer.subcategory); | |||
| } | |||
| }, [props.offer, props.informations]); | |||
| useEffect(() => { | |||
| if (props.offer !== undefined) { | |||
| let scat = categories.filter( | |||
| (cat) => cat.name === props.offer.category.name | |||
| ); | |||
| console.log(scat[0].subcategories.map((x) => x.name)); | |||
| setSubcat(scat[0].subcategories.map((x) => x.name)); | |||
| } | |||
| }, [props.offer]); | |||
| @@ -39,15 +58,11 @@ const FirstPartCreateOffer = (props) => { | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| nameOfProduct: `${props.offer === undefined ? "" : props.offer.name}`, | |||
| description: `${ | |||
| props.offer === undefined ? "" : props.offer.description | |||
| }`, | |||
| location: `${props.offer === undefined ? "" : props.offer.location.city}`, | |||
| category: `${props.offer === undefined ? "" : props.offer.category.name}`, | |||
| subcategory: `${ | |||
| props.offer === undefined ? "" : props.offer.subcategory | |||
| }`, | |||
| nameOfProduct: `${!props.offer ? "" : props.offer.name}`, | |||
| description: `${!props.offer ? "" : props.offer.description}`, | |||
| location: "default", | |||
| category: "default", | |||
| subcategory: "default", | |||
| }, | |||
| validationSchema: Yup.object().shape({ | |||
| nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")), | |||
| @@ -119,19 +134,18 @@ const FirstPartCreateOffer = (props) => { | |||
| <FieldLabel leftText={t("offer.location")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.location.city | |||
| } | |||
| defaultValue={formik.values.location} | |||
| onChange={(value) => { | |||
| formik.setFieldValue("location", value.target.value); | |||
| }} | |||
| value={formik.values.location} | |||
| > | |||
| <SelectOption value="default"> | |||
| {t("offer.choseLocation")} | |||
| </SelectOption> | |||
| {locations.map((loc) => { | |||
| return ( | |||
| <SelectOption key={loc._if} value={loc.city}> | |||
| <SelectOption key={loc._id} value={loc.city}> | |||
| {loc.city} | |||
| </SelectOption> | |||
| ); | |||
| @@ -140,12 +154,11 @@ const FirstPartCreateOffer = (props) => { | |||
| <FieldLabel leftText={t("offer.category")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.category.name | |||
| } | |||
| defaultValue={formik.values.category} | |||
| onChange={(value) => { | |||
| formik.setFieldValue("category", value.target.value); | |||
| }} | |||
| value={formik.values.category} | |||
| > | |||
| <SelectOption value="default"> | |||
| {t("offer.choseCategory")} | |||
| @@ -165,13 +178,11 @@ const FirstPartCreateOffer = (props) => { | |||
| <FieldLabel leftText={t("offer.subcategory")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.subcategory | |||
| } | |||
| // defaultValue="default" | |||
| defaultValue={formik.values.subcategory} | |||
| onChange={(value) => { | |||
| formik.setFieldValue("subcategory", value.target.value); | |||
| }} | |||
| value={formik.values.subcategory} | |||
| > | |||
| <SelectOption value="default"> | |||
| {t("offer.choseSubcategory")} | |||
| @@ -202,10 +213,13 @@ const FirstPartCreateOffer = (props) => { | |||
| !formik.values?.description || | |||
| formik.values?.category?.length === 0 || | |||
| !formik.values?.category || | |||
| formik.values?.category === "default" || | |||
| formik.values?.subcategory?.length === 0 || | |||
| !formik.values?.subcategory || | |||
| formik.values?.subcategory === "default" || | |||
| formik.values?.location?.length === 0 || | |||
| !formik.values?.location | |||
| !formik.values?.location || | |||
| formik.values?.location === "default" | |||
| } | |||
| > | |||
| {t("offer.continue")} | |||
| @@ -218,6 +232,7 @@ FirstPartCreateOffer.propTypes = { | |||
| children: PropTypes.any, | |||
| handleNext: PropTypes.func, | |||
| offer: PropTypes.node, | |||
| informations: PropTypes.any, | |||
| }; | |||
| export default FirstPartCreateOffer; | |||
| @@ -27,6 +27,17 @@ const SecondPartCreateOffer = (props) => { | |||
| ); // 3 images | |||
| const { t } = useTranslation(); | |||
| useEffect(() => { | |||
| if (!props.offer) { | |||
| if (Object.keys(props.informations).length > 5) { | |||
| setImages([...props.informations.images]); | |||
| formik.setFieldValue("condition", props.informations.condition); | |||
| } | |||
| } else { | |||
| formik.setFieldValue("condition", props.offer.condition); | |||
| } | |||
| }, [props.offer, props.informations]); | |||
| useEffect(() => { | |||
| setImages((prevState) => { | |||
| let editedImages = [...prevState]; | |||
| @@ -63,12 +74,21 @@ const SecondPartCreateOffer = (props) => { | |||
| props.handleNext(values); | |||
| }; | |||
| const conditionSelectEnumArray = Object.values(conditionSelectEnum); | |||
| const filteredconditionSelectEnumArray = conditionSelectEnumArray.map( | |||
| (item) => item.mainText | |||
| ); | |||
| const formik = useFormik({ | |||
| initialValues: { | |||
| images: images, | |||
| condition: `${props.offer === undefined ? "" : props.offer.condition}`, | |||
| condition: props.informations?.condition || "default", | |||
| }, | |||
| validationSchema: Yup.object().shape({}), | |||
| validationSchema: Yup.object().shape({ | |||
| condition: Yup.string() | |||
| .required() | |||
| .oneOf(filteredconditionSelectEnumArray), | |||
| }), | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| @@ -96,12 +116,10 @@ const SecondPartCreateOffer = (props) => { | |||
| <InputButtonContainer> | |||
| <FieldLabel leftText={t("offer.condition")} /> | |||
| <SelectField | |||
| defaultValue={ | |||
| props.offer === undefined ? "default" : props.offer.condition | |||
| } | |||
| onChange={(value) => { | |||
| formik.setFieldValue("condition", value.target.value); | |||
| }} | |||
| value={formik.values.condition} | |||
| > | |||
| <SelectOption value="default"> | |||
| {t("offer.choseCondition")} | |||
| @@ -127,7 +145,12 @@ const SecondPartCreateOffer = (props) => { | |||
| textcolor="white" | |||
| onClick={formik.handleSubmit} | |||
| disabled={ | |||
| props.offer === undefined ? imagesEmpty === numberOfImages : false | |||
| (props.offer === undefined | |||
| ? imagesEmpty === numberOfImages | |||
| : false) || | |||
| formik.values?.condition?.length === 0 || | |||
| !formik.values?.condition || | |||
| formik.values?.condition === "default" | |||
| } | |||
| > | |||
| {t("offer.continue")} | |||
| @@ -140,6 +163,7 @@ SecondPartCreateOffer.propTypes = { | |||
| children: PropTypes.node, | |||
| handleNext: PropTypes.func, | |||
| offer: PropTypes.node, | |||
| informations: PropTypes.any, | |||
| }; | |||
| export default SecondPartCreateOffer; | |||
| @@ -33,7 +33,10 @@ const FilterCard = (props) => { | |||
| <LocationChoser filters={filters} /> | |||
| </ContentContainer> | |||
| <FilterFooter /> | |||
| <FilterFooter | |||
| closeResponsive={props.closeResponsive} | |||
| responsiveOpen={props.responsiveOpen} | |||
| /> | |||
| </FilterCardContainer> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,32 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { CheckBox as CheckboxButton } from "../../../../../CheckBox/CheckBox"; | |||
| const Checkbox = (props) => { | |||
| const item = props.item; | |||
| return ( | |||
| <CheckboxButton | |||
| leftText={item.city} | |||
| rightText={item.offerCount} | |||
| value={item} | |||
| checked={ | |||
| props.filters.find( | |||
| (itemInList) => | |||
| itemInList?.city?.toString() === item?.city?.toString() | |||
| ) | |||
| ? true | |||
| : false | |||
| } | |||
| onChange={props.onChange} | |||
| fullWidth | |||
| /> | |||
| ); | |||
| }; | |||
| Checkbox.propTypes = { | |||
| item: PropTypes.any, | |||
| filters: PropTypes.any, | |||
| onChange: PropTypes.func, | |||
| }; | |||
| export default Checkbox; | |||
| @@ -0,0 +1,84 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import DropdownList from "../../../../../Dropdown/DropdownList/DropdownList"; | |||
| import selectedTheme from "../../../../../../themes"; | |||
| import IconWithNumber from "../../../../../Icon/IconWithNumber/IconWithNumber"; | |||
| import { ReactComponent as DropdownDown } from "../../../../../../assets/images/svg/down-arrow.svg"; | |||
| import { ReactComponent as DropdownUp } from "../../../../../../assets/images/svg/up-arrow.svg"; | |||
| import { ReactComponent as Close } from "../../../../../../assets/images/svg/close-white.svg"; | |||
| import { | |||
| SelectedItem, | |||
| SelectedItemsContainer, | |||
| } from "./CheckboxDropdownList.styled"; | |||
| import SearchField from "./SearchField/SearchField"; | |||
| const CheckboxDropdownList = (props) => { | |||
| const data = props.data; | |||
| const handleDelete = (item) => { | |||
| props.setItemsSelected([...props.filters.filter((p) => p !== item)]); | |||
| }; | |||
| return ( | |||
| <DropdownList | |||
| title={props.title} | |||
| textcolor={ | |||
| props.filters.length > 0 | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.primaryText | |||
| } | |||
| dropdownIcon={ | |||
| <IconWithNumber number={props.filters.length}> | |||
| {props.icon} | |||
| </IconWithNumber> | |||
| } | |||
| toggleIconClosed={<DropdownDown />} | |||
| toggleIconOpened={<DropdownUp />} | |||
| fullWidth | |||
| open={props.isOpened} | |||
| setIsOpened={props.setIsOpened} | |||
| toggleIconStyles={{ | |||
| backgroundColor: props.isOpened | |||
| ? "white" | |||
| : selectedTheme.primaryIconBackgroundColor, | |||
| }} | |||
| headerOptions={ | |||
| <React.Fragment> | |||
| <SelectedItemsContainer> | |||
| {props.filters.map((item) => ( | |||
| <SelectedItem key={item.city} onClick={() => handleDelete(item)}> | |||
| { | |||
| data.find( | |||
| (p) => p?.city?.toString() === item?.city?.toString() | |||
| )?.city | |||
| } | |||
| <Close style={{ position: "relative", top: "3px" }} /> | |||
| </SelectedItem> | |||
| ))} | |||
| </SelectedItemsContainer> | |||
| <SearchField | |||
| placeholder={props.searchPlaceholder} | |||
| value={props.toSearch} | |||
| onChange={(event) => props.setToSearch(event.target.value)} | |||
| /> | |||
| </React.Fragment> | |||
| } | |||
| > | |||
| {props.children} | |||
| </DropdownList> | |||
| ); | |||
| }; | |||
| CheckboxDropdownList.propTypes = { | |||
| children: PropTypes.node, | |||
| title: PropTypes.string, | |||
| filters: PropTypes.any, | |||
| icon: PropTypes.node, | |||
| setToSearch: PropTypes.func, | |||
| setItemsSelected: PropTypes.func, | |||
| data: PropTypes.any, | |||
| searchPlaceholder: PropTypes.string, | |||
| toSearch: PropTypes.string, | |||
| isOpened: PropTypes.bool, | |||
| setIsOpened: PropTypes.func, | |||
| }; | |||
| export default CheckboxDropdownList; | |||
| @@ -0,0 +1,25 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../../themes"; | |||
| export const SelectedItemsContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex-wrap: wrap; | |||
| margin-top: 5px; | |||
| `; | |||
| export const SelectedItem = styled(Box)` | |||
| margin-top: 2px; | |||
| background-color: ${selectedTheme.primaryPurple}; | |||
| border-radius: 8px; | |||
| color: white; | |||
| padding-left: 8px; | |||
| padding-right: 6px; | |||
| line-height: 12px; | |||
| letter-spacing: 0.02em; | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| cursor: pointer; | |||
| margin-right: 3px; | |||
| height: 22px; | |||
| `; | |||
| @@ -0,0 +1,42 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { TextField } from "../../../../../../TextFields/TextField/TextField"; | |||
| import { ReactComponent as CloseBlack } from "../../../../../../../assets/images/svg/close-black.svg"; | |||
| import { ClearText } from "./SearchField.styled"; | |||
| const SearchField = (props) => { | |||
| const handleClear = () => { | |||
| props.onChange(""); | |||
| }; | |||
| return ( | |||
| <TextField | |||
| placeholder={props.placeholder} | |||
| italicPlaceholder | |||
| value={props.value} | |||
| onChange={props.onChange} | |||
| textsize={"12px"} | |||
| font={"Open Sans"} | |||
| fullWidth | |||
| height={"40px"} | |||
| containerStyle={{ marginTop: "6px" }} | |||
| InputProps={{ | |||
| endAdornment: | |||
| props.value.length > 0 ? ( | |||
| <ClearText onClick={handleClear}> | |||
| <CloseBlack /> | |||
| </ClearText> | |||
| ) : ( | |||
| <React.Fragment /> | |||
| ), | |||
| }} | |||
| /> | |||
| ); | |||
| }; | |||
| SearchField.propTypes = { | |||
| value: PropTypes.string, | |||
| placeholder: PropTypes.string, | |||
| onChange: PropTypes.func, | |||
| }; | |||
| export default SearchField; | |||
| @@ -0,0 +1,17 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../../../themes"; | |||
| export const ClearText = styled(Box)` | |||
| padding-top: 1px; | |||
| border-radius: 100%; | |||
| cursor: pointer; | |||
| padding-right: 2px; | |||
| position: relative; | |||
| left: 6px; | |||
| width: 21px; | |||
| height: 21px; | |||
| &:hover { | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| } | |||
| `; | |||
| @@ -1,30 +1,19 @@ | |||
| import React, { useEffect, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import DropdownList from "../../../../Dropdown/DropdownList/DropdownList"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import IconWithNumber from "../../../../Icon/IconWithNumber/IconWithNumber"; | |||
| import { ReactComponent as DropdownDown } from "../../../../../assets/images/svg/down-arrow.svg"; | |||
| import { ReactComponent as DropdownUp } from "../../../../../assets/images/svg/up-arrow.svg"; | |||
| import { ReactComponent as Close } from "../../../../../assets/images/svg/close-white.svg"; | |||
| import { ReactComponent as CloseBlack } from "../../../../../assets/images/svg/close-black.svg"; | |||
| import { | |||
| ClearText, | |||
| SelectedItem, | |||
| SelectedItemsContainer, | |||
| } from "./FilterCheckboxDropdown.styled"; | |||
| import { TextField } from "../../../../TextFields/TextField/TextField"; | |||
| import DropdownItem from "../../../../Dropdown/DropdownItem/DropdownItem"; | |||
| import { CheckBox } from "../../../../CheckBox/CheckBox"; | |||
| import CheckboxDropdownList from "./CheckboxDropdownList/CheckboxDropdownList"; | |||
| import Checkbox from "./Checkbox/Checkbox"; | |||
| const FilterCheckboxDropdown = (props) => { | |||
| const [toSearch, setToSearch] = useState(""); | |||
| const [dataToShow, setDataToShow] = useState([]); | |||
| const [isOpened, setIsOpened] = useState(false); | |||
| const [toSearch, setToSearch] = useState(""); | |||
| const { data } = props; | |||
| useEffect(() => { | |||
| setDataToShow([...data]); | |||
| }, [data]); | |||
| useEffect(() => { | |||
| if (toSearch.length > 0) { | |||
| setDataToShow( | |||
| @@ -37,110 +26,57 @@ const FilterCheckboxDropdown = (props) => { | |||
| } | |||
| }, [toSearch]); | |||
| useEffect(() => { | |||
| if (props.filters?.length > 0) { | |||
| setIsOpened(true) | |||
| setIsOpened(true); | |||
| } | |||
| }, [props.filters]) | |||
| }, [props.filters]); | |||
| const handleChange = (item) => { | |||
| if (props.oneValueAllowed) { | |||
| props.setItemsSelected([item]); | |||
| } else { | |||
| if (props.filters.find(itemInList => itemInList?.city?.toString() === item?.city?.toString())) { | |||
| props.setItemsSelected([...props.filters.filter((p) => p?.city?.toString() !== item?.city?.toString())]); | |||
| if ( | |||
| props.filters.find( | |||
| (itemInList) => | |||
| itemInList?.city?.toString() === item?.city?.toString() | |||
| ) | |||
| ) { | |||
| props.setItemsSelected([ | |||
| ...props.filters.filter( | |||
| (p) => p?.city?.toString() !== item?.city?.toString() | |||
| ), | |||
| ]); | |||
| } else { | |||
| props.setItemsSelected([...props.filters, item]); | |||
| } | |||
| } | |||
| }; | |||
| const handleDelete = (item) => { | |||
| props.setItemsSelected([...props.filters.filter((p) => p !== item)]); | |||
| }; | |||
| const handleClear = () => { | |||
| setToSearch(""); | |||
| }; | |||
| return ( | |||
| <DropdownList | |||
| <CheckboxDropdownList | |||
| toSearch={toSearch} | |||
| setToSearch={setToSearch} | |||
| title={props.title} | |||
| textcolor={ | |||
| props.filters.length > 0 | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.primaryText | |||
| } | |||
| dropdownIcon={ | |||
| <IconWithNumber number={props.filters.length}> | |||
| {props.icon} | |||
| </IconWithNumber> | |||
| } | |||
| toggleIconClosed={<DropdownDown />} | |||
| toggleIconOpened={<DropdownUp />} | |||
| fullWidth | |||
| open={isOpened} | |||
| filters={props.filters} | |||
| icon={props.icon} | |||
| data={data} | |||
| searchPlaceholder={props.searchPlaceholder} | |||
| isOpened={isOpened} | |||
| setIsOpened={setIsOpened} | |||
| toggleIconStyles={{ | |||
| backgroundColor: isOpened | |||
| ? "white" | |||
| : selectedTheme.primaryIconBackgroundColor, | |||
| }} | |||
| headerOptions={ | |||
| <React.Fragment> | |||
| <SelectedItemsContainer> | |||
| {props.filters.map((item) => ( | |||
| <SelectedItem key={item.city} onClick={() => handleDelete(item)}> | |||
| { | |||
| data.find((p) => p?.city?.toString() === item?.city?.toString()) | |||
| ?.city | |||
| } | |||
| <Close style={{ position: "relative", top: "3px" }} /> | |||
| </SelectedItem> | |||
| ))} | |||
| </SelectedItemsContainer> | |||
| <TextField | |||
| placeholder={props.searchPlaceholder} | |||
| italicPlaceholder | |||
| value={toSearch} | |||
| onChange={(event) => setToSearch(event.target.value)} | |||
| textsize={"12px"} | |||
| font={"Open Sans"} | |||
| fullWidth | |||
| height={"40px"} | |||
| containerStyle={{ marginTop: "6px" }} | |||
| InputProps={{ | |||
| endAdornment: | |||
| toSearch.length > 0 ? ( | |||
| <ClearText onClick={handleClear}> | |||
| <CloseBlack /> | |||
| </ClearText> | |||
| ) : ( | |||
| <React.Fragment /> | |||
| ), | |||
| }} | |||
| /> | |||
| </React.Fragment> | |||
| } | |||
| > | |||
| {dataToShow.map((item) => { | |||
| return ( | |||
| <DropdownItem key={item.city}> | |||
| <CheckBox | |||
| leftText={item.city} | |||
| rightText={item.offerCount} | |||
| value={item} | |||
| checked={props.filters.find( | |||
| (itemInList) => | |||
| itemInList?.city?.toString() === item?.city?.toString() | |||
| ) ? true : false} | |||
| <Checkbox | |||
| item={item} | |||
| filters={props.filters} | |||
| onChange={() => handleChange(item)} | |||
| fullWidth | |||
| /> | |||
| </DropdownItem> | |||
| ); | |||
| })} | |||
| </DropdownList> | |||
| </CheckboxDropdownList> | |||
| ); | |||
| }; | |||
| @@ -157,5 +93,4 @@ FilterCheckboxDropdown.propTypes = { | |||
| FilterCheckboxDropdown.defaultProps = { | |||
| oneValueAllowed: false, | |||
| }; | |||
| export default FilterCheckboxDropdown; | |||
| @@ -1,38 +1,5 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| // import { Box } from "@mui/material"; | |||
| // import styled from "styled-components"; | |||
| // import selectedTheme from "../../../../../themes"; | |||
| export const SelectedItemsContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex-wrap: wrap; | |||
| margin-top: 5px; | |||
| `; | |||
| export const SelectedItem = styled(Box)` | |||
| margin-top: 2px; | |||
| background-color: ${selectedTheme.primaryPurple}; | |||
| border-radius: 8px; | |||
| color: white; | |||
| padding-left: 8px; | |||
| padding-right: 6px; | |||
| line-height: 12px; | |||
| letter-spacing: 0.02em; | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| cursor: pointer; | |||
| margin-right: 3px; | |||
| height: 22px; | |||
| `; | |||
| export const ClearText = styled(Box)` | |||
| padding-top: 1px; | |||
| border-radius: 100%; | |||
| cursor: pointer; | |||
| padding-right: 2px; | |||
| position: relative; | |||
| left: 6px; | |||
| width: 21px; | |||
| height: 21px; | |||
| &:hover { | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| } | |||
| `; | |||
| @@ -124,7 +124,7 @@ const FilterRadioDropdown = (props) => { | |||
| > | |||
| <RadioButton | |||
| value={item} | |||
| label={item.name ? item.name : item} | |||
| label={item?.name ? item?.name : item?.length > 0 ? item : ""} | |||
| number={item.offerCount} | |||
| fullWidth | |||
| checked={ | |||
| @@ -14,7 +14,7 @@ const FilterFooter = (props) => { | |||
| if (props.closeResponsive) props.closeResponsive(); | |||
| }; | |||
| return ( | |||
| <FilterFooterContainer> | |||
| <FilterFooterContainer responsiveOpen={props.responsiveOpen}> | |||
| {props.responsiveOpen && ( | |||
| <PrimaryButton | |||
| variant="outlined" | |||
| @@ -7,13 +7,6 @@ import { | |||
| Info, | |||
| ButtonsContainer, | |||
| PostDate, | |||
| OfferTitle, | |||
| OfferDescriptionText, | |||
| OfferDescriptionTitle, | |||
| Details, | |||
| OfferDetails, | |||
| OfferImage, | |||
| Scroller, | |||
| CategoryIcon, | |||
| SubcategoryIcon, | |||
| QuantityIcon, | |||
| @@ -36,6 +29,7 @@ import Information from "./Information/Information"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import DeleteOffer from "../OfferCard/DeleteOffer/DeleteOffer"; | |||
| import CreateOffer from "../CreateOfferCard/CreateOffer"; | |||
| import OfferDetails from "./OfferDetails/OfferDetails"; | |||
| const ItemDetailsCard = (props) => { | |||
| const [showModalRemove, setShowModalRemove] = useState(false); | |||
| @@ -67,7 +61,7 @@ const ItemDetailsCard = (props) => { | |||
| const date = formatDateLocale(new Date(offer?.offer?._created)); | |||
| const startExchange = () => { | |||
| startChat(chats, offer, userId); | |||
| startChat(chats, offer?.offer, userId); | |||
| }; | |||
| const closeEditModalHandler = () => { | |||
| @@ -120,25 +114,12 @@ const ItemDetailsCard = (props) => { | |||
| )} | |||
| </PostDate> | |||
| </OfferInfo> | |||
| <Details | |||
| hasScrollBar={!props.showPublishButton} | |||
| exchange={props.showExchangeButton} | |||
| > | |||
| <OfferTitle>{offer?.offer?.name}</OfferTitle> | |||
| <Scroller> | |||
| {offer?.offer?.images?.map((item) => { | |||
| return <OfferImage src={item} key={item} />; | |||
| })} | |||
| </Scroller> | |||
| <OfferDetails> | |||
| <OfferDescriptionTitle> | |||
| {t("itemDetailsCard.description")} | |||
| </OfferDescriptionTitle> | |||
| <OfferDescriptionText showBarterButton={props.showExchangeButton}> | |||
| {offer?.offer?.description} | |||
| </OfferDescriptionText> | |||
| </OfferDetails> | |||
| </Details> | |||
| <OfferDetails | |||
| offer={offer} | |||
| showExchangeButton={props.showExchangeButton} | |||
| showPublishButton={props.showPublishButton} | |||
| /> | |||
| {!props.halfwidth && props.showExchangeButton && ( | |||
| <CheckButton | |||
| variant={props.sponsored ? "contained" : "outlined"} | |||
| @@ -4,7 +4,6 @@ import selectedTheme from "../../../themes"; | |||
| //import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Icon } from "../../Icon/Icon"; | |||
| import HorizontalScroller from "../../Scroller/HorizontalScroller"; | |||
| 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"; | |||
| @@ -149,31 +148,6 @@ export const Info = styled(Box)` | |||
| left: 5px; | |||
| } | |||
| `; | |||
| export const OfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 24px; | |||
| padding: 0 60px; | |||
| @media screen and (max-width: 600px) { | |||
| padding: 0; | |||
| font-size: 18px; | |||
| } | |||
| `; | |||
| export const OfferImage = styled.img` | |||
| min-width: 144px; | |||
| min-height: 144px; | |||
| width: 144px; | |||
| height: 144px; | |||
| margin-right: 20px; | |||
| object-fit: cover; | |||
| @media screen and (max-width: 600px) { | |||
| min-width: 144px; | |||
| margin-right: 13px; | |||
| } | |||
| `; | |||
| export const OfferAuthor = styled(Box)` | |||
| display: flex; | |||
| flex: 1; | |||
| @@ -191,16 +165,7 @@ export const OfferLocation = styled(Typography)` | |||
| line-height: 16px; | |||
| font-size: 12px; | |||
| `; | |||
| export const OfferDetails = styled(Box)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")}; | |||
| justify-content: space-between; | |||
| padding: 0 60px; | |||
| @media (max-width: 600px) { | |||
| padding: 0; | |||
| } | |||
| `; | |||
| export const OfferCategory = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| @@ -318,14 +283,6 @@ export const Details = styled(Box)` | |||
| `; | |||
| // export const OfferImage = styled.img` | |||
| // ` | |||
| export const Scroller = styled(HorizontalScroller)` | |||
| min-height: 144px; | |||
| min-width: 144px; | |||
| max-width: 100%; | |||
| /* & div { | |||
| margin: 0 9px; | |||
| } */ | |||
| `; | |||
| export const PublishButtonContainer = styled(Box)` | |||
| display: flex; | |||
| @@ -1,36 +0,0 @@ | |||
| import React from "react" | |||
| import {ReactComponent as DummyImage1 } from "../../../assets/images/svg/dummyImages/offer-1.svg" | |||
| export const Offer = { | |||
| id: 0, | |||
| title: "Vino", | |||
| category: "Hrana i pice", | |||
| subcategory:"Farbe", | |||
| status:"novo", | |||
| quantity:150, | |||
| numberOfViews:45, | |||
| description: "Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.", | |||
| images: [ | |||
| { | |||
| id:0, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:1, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:2, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:3, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:4, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| ], | |||
| postDate: "12.04.2022", | |||
| } | |||
| @@ -1,14 +1,46 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| Details, | |||
| OfferDescriptionText, | |||
| OfferDescriptionTitle, | |||
| OfferImage, | |||
| OfferLittleDetails, | |||
| OfferTitle, | |||
| Scroller, | |||
| } from "./OfferDetails.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const OfferDetails = () => { | |||
| const OfferDetails = (props) => { | |||
| const offer = props.offer; | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <div>OfferDetails</div> | |||
| ) | |||
| } | |||
| <Details | |||
| hasScrollBar={!props.showPublishButton} | |||
| exchange={props.showExchangeButton} | |||
| > | |||
| <OfferTitle>{offer?.offer?.name}</OfferTitle> | |||
| <Scroller> | |||
| {offer?.offer?.images?.map((item) => { | |||
| return <OfferImage src={item} key={item} />; | |||
| })} | |||
| </Scroller> | |||
| <OfferLittleDetails> | |||
| <OfferDescriptionTitle> | |||
| {t("itemDetailsCard.description")} | |||
| </OfferDescriptionTitle> | |||
| <OfferDescriptionText showBarterButton={props.showExchangeButton}> | |||
| {offer?.offer?.description} | |||
| </OfferDescriptionText> | |||
| </OfferLittleDetails> | |||
| </Details> | |||
| ); | |||
| }; | |||
| OfferDetails.propTypes = { | |||
| offer: PropTypes.any, | |||
| } | |||
| offer: PropTypes.any, | |||
| showExchangeButton: PropTypes.bool, | |||
| showPublishButton: PropTypes.bool, | |||
| }; | |||
| export default OfferDetails | |||
| export default OfferDetails; | |||
| @@ -0,0 +1,101 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| import HorizontalScroller from "../../../Scroller/HorizontalScroller"; | |||
| export const Details = styled(Box)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 12px; | |||
| ${(props) => props.hasScrollBar && !props.exchange && `height: 300px;`} | |||
| overflow-y: auto; | |||
| overflow-x: hidden; | |||
| ::-webkit-scrollbar { | |||
| width: 5px; | |||
| } | |||
| ::-webkit-scrollbar-thumb { | |||
| background: #c4c4c4; | |||
| border-radius: 3px; | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| margin-top: 15px; | |||
| ${(props) => | |||
| !props.hasScrollBar && props.exchange && | |||
| ` | |||
| overflow: hidden; | |||
| max-height: none;`} | |||
| } | |||
| `; | |||
| export const OfferTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| flex: 1; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| font-weight: 700; | |||
| font-size: 24px; | |||
| padding: 0 60px; | |||
| @media screen and (max-width: 600px) { | |||
| padding: 0; | |||
| font-size: 18px; | |||
| } | |||
| `; | |||
| export const OfferLittleDetails = styled(Box)` | |||
| display: flex; | |||
| flex-direction: column; | |||
| flex-wrap: ${(props) => (!props.halfwidth ? "no-wrap" : "wrap")}; | |||
| justify-content: space-between; | |||
| padding: 0 60px; | |||
| @media (max-width: 600px) { | |||
| padding: 0; | |||
| } | |||
| `; | |||
| export const Scroller = styled(HorizontalScroller)` | |||
| min-height: 144px; | |||
| min-width: 144px; | |||
| max-width: 100%; | |||
| /* & div { | |||
| margin: 0 9px; | |||
| } */ | |||
| `; | |||
| export const OfferDescriptionTitle = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: 12px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| line-height: 16px; | |||
| @media (max-width: 600px) { | |||
| font-size: 9px; | |||
| line-height: 13px; | |||
| } | |||
| `; | |||
| export const OfferDescriptionText = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| font-size: 16px; | |||
| color: ${selectedTheme.primaryDarkText}; | |||
| line-height: 22px; | |||
| padding-bottom: 20px; | |||
| max-width: ${(props) => props.showBarterButton ? "calc(100% - 230px)" : "100%"}; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| max-width: 100%; | |||
| max-height: 100px; | |||
| } | |||
| /* max-width: calc(100% - 230px); */ | |||
| /* overflow: hidden; */ | |||
| /* display: -webkit-box; | |||
| -webkit-line-clamp: 5; | |||
| -webkit-box-orient: vertical; */ | |||
| `; | |||
| export const OfferImage = styled.img` | |||
| min-width: 144px; | |||
| min-height: 144px; | |||
| width: 144px; | |||
| height: 144px; | |||
| margin-right: 20px; | |||
| object-fit: cover; | |||
| @media screen and (max-width: 600px) { | |||
| min-width: 144px; | |||
| margin-right: 13px; | |||
| } | |||
| `; | |||
| @@ -11,14 +11,16 @@ import { formatDateTime } from "../../../util/helpers/dateHelpers"; | |||
| const MessageCard = (props) => { | |||
| const message = props.message; | |||
| const dateString = formatDateTime(new Date(message._created)) | |||
| 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> | |||
| <MessageText isMyMessage={props.isMyMessage}> | |||
| {props.message.text} | |||
| </MessageText> | |||
| <MessageDate isMyMessage={props.isMyMessage}>{dateString}</MessageDate> | |||
| </MessageContent> | |||
| </MessageCardContainer> | |||
| ); | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState } from "react"; | |||
| import React, { useMemo, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| CheckButton, | |||
| @@ -36,11 +36,14 @@ import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import CreateOffer from "../CreateOfferCard/CreateOffer"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| const OfferCard = (props) => { | |||
| const [deleteOfferModal, setDeleteOfferModal] = useState(false); | |||
| const [editOfferModal, setEditOfferModal] = useState(false); | |||
| const history = useHistory(); | |||
| const userId = useSelector(selectUserId); | |||
| const routeToItem = (itemId) => { | |||
| history.push(`/proizvodi/${itemId}`); | |||
| @@ -62,6 +65,13 @@ const OfferCard = (props) => { | |||
| setEditOfferModal(false); | |||
| }; | |||
| const showMessageIcon = useMemo(() => { | |||
| if (userId === props.offer?.userId) { | |||
| return false; | |||
| } | |||
| return true; | |||
| }, [userId, props.offer]); | |||
| if (deleteOfferModal || editOfferModal) { | |||
| document.body.style.overflow = "hidden"; | |||
| } else { | |||
| @@ -175,7 +185,11 @@ const OfferCard = (props) => { | |||
| <StarIcon disabled={props.disabledReviews} /> | |||
| </StarIconContainer> | |||
| ) : ( | |||
| <MessageIcon vertical={props.vertical} onClick={messageUser}> | |||
| <MessageIcon | |||
| showMessageIcon={showMessageIcon} | |||
| vertical={props.vertical} | |||
| onClick={messageUser} | |||
| > | |||
| <Message /> | |||
| </MessageIcon> | |||
| )} | |||
| @@ -254,6 +254,7 @@ export const CheckButton = styled(PrimaryButton)` | |||
| } | |||
| `; | |||
| export const MessageIcon = styled(IconButton)` | |||
| ${(props) => !props.showMessageIcon && "display: none;"} | |||
| width: 40px; | |||
| height: 40px; | |||
| position: absolute; | |||
| @@ -331,12 +332,16 @@ export const RemoveIconContainer = styled(MessageIcon)` | |||
| `; | |||
| export const RemoveIcon = styled(Remove)``; | |||
| export const EditIconContainer = styled(MessageIcon)` | |||
| display: block; | |||
| right: 70px; | |||
| `; | |||
| export const EditIcon = styled(Edit)``; | |||
| export const StarIconContainer = styled(MessageIcon)` | |||
| opacity: ${props => props.disabled ? "0.4" : "1"}; | |||
| ${props => props.disabled && ` | |||
| display: block; | |||
| opacity: ${(props) => (props.disabled ? "0.4" : "1")}; | |||
| ${(props) => | |||
| props.disabled && | |||
| ` | |||
| cursor: initial; | |||
| & button { | |||
| cursor: initial; | |||
| @@ -1,24 +0,0 @@ | |||
| export default [{ | |||
| id: 0, | |||
| name: "Coca-Cola", | |||
| quote: "Odlična saradnja. Sve preporuke za kompaniju", | |||
| isGood: true, | |||
| isGoodCommunication: "DA", | |||
| isSuccessfulSwap: "DA" | |||
| } | |||
| ,{ | |||
| id: 1, | |||
| name: "Voda Vrnjci", | |||
| quote: "Sasvim korektna saradnja, rado bih ponovio poslovanje sa Vama.", | |||
| isGood: true, | |||
| isGoodCommunication: "DA", | |||
| isSuccessfulSwap: "DA" | |||
| } | |||
| ,{ | |||
| id: 2, | |||
| name: "Women's Beauty House", | |||
| quote: "Nismo se najbolje razumeli, ali generalno ok", | |||
| isGood: false, | |||
| isGoodCommunication: "NE", | |||
| isSuccessfulSwap: "NE" | |||
| }]; | |||
| @@ -15,56 +15,35 @@ import { | |||
| 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 | |||
| } | |||
| ...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 === "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 | |||
| } | |||
| 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 }}> | |||
| @@ -84,17 +63,14 @@ const UserReviewsCard = (props) => { | |||
| sx={{ pl: 2, py: 2 }} | |||
| > | |||
| <ThumbBox item> | |||
| {isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />} | |||
| {review.isSuccessfulSwap ? ( | |||
| <ThumbUp color="success" /> | |||
| ) : ( | |||
| <ThumbDown color="error" /> | |||
| )} | |||
| </ThumbBox> | |||
| <ReviewQuoteBox item> | |||
| <ReviewQuoteText | |||
| sx={{ display: "inline" }} | |||
| component="span" | |||
| variant="body2" | |||
| color="text.primary" | |||
| > | |||
| "{review?.quote}" | |||
| </ReviewQuoteText> | |||
| <ReviewQuoteText>"{review?.quote}"</ReviewQuoteText> | |||
| </ReviewQuoteBox> | |||
| </ReviewQuote> | |||
| <ReviewDetails sx={{ pl: 2, pb: 2 }}> | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState, useEffect } from "react"; | |||
| import React, { useState, useEffect } from "react"; | |||
| import ChatCard from "../Cards/ChatCard/ChatCard"; | |||
| import { | |||
| ChatColumnContainer, | |||
| @@ -36,7 +36,7 @@ export const ChatColumn = () => { | |||
| useEffect(() => { | |||
| dispatch(fetchChats()); | |||
| }, []) | |||
| }, []); | |||
| useEffect(() => { | |||
| setSortOption(sorting.selectedSortOption); | |||
| @@ -82,7 +82,7 @@ export const ChatColumn = () => { | |||
| })} | |||
| </HeaderSelect> | |||
| </TitleSortContainer> | |||
| <ListHeader enableSort={true}></ListHeader> | |||
| <ListHeader enableSort={true} /> | |||
| <ListContainer> | |||
| {chats.map((item, index) => ( | |||
| <ChatCard key={index} chat={item} /> | |||
| @@ -9,15 +9,14 @@ import { | |||
| } from "./CreateReview.styled"; | |||
| import FirstStepCreateReview from "./FirstStep/FirstStepCreateReview"; | |||
| import SecondStepCreateReview from "./SecondStep/SecondStepCreateReview"; | |||
| import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview"; | |||
| 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(); | |||
| @@ -29,15 +28,12 @@ const CreateReview = (props) => { | |||
| props.handleGiveReviewSuccess(); | |||
| }; | |||
| const submitForm = () => { | |||
| let communication; | |||
| if (informations.correctCommunication === reviewEnum.YES.mainText) | |||
| communication = "yes"; | |||
| let 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"; | |||
| let succeeded = "failed"; | |||
| if (informations.exchangeSucceed === reviewEnum.YES.mainText) | |||
| succeeded = "succeeded"; | |||
| dispatch( | |||
| @@ -55,10 +51,6 @@ const CreateReview = (props) => { | |||
| }; | |||
| const goToNextStep = (newInformations) => { | |||
| setInformations((prevInformations) => { | |||
| console.log({ | |||
| ...prevInformations, | |||
| ...newInformations, | |||
| }); | |||
| return { | |||
| ...prevInformations, | |||
| ...newInformations, | |||
| @@ -87,12 +79,10 @@ const CreateReview = (props) => { | |||
| <CloseButton onClick={closeModal}> | |||
| <CloseIcon /> | |||
| </CloseButton> | |||
| {currentStep === 2 ? ( | |||
| {currentStep === 2 && ( | |||
| <BackIcon onClick={goToPrevStep}> | |||
| <ArrowBackIcon /> | |||
| </BackIcon> | |||
| ) : ( | |||
| "" | |||
| )} | |||
| {currentStep === 1 && ( | |||
| <FirstStepCreateReview | |||
| @@ -89,14 +89,16 @@ const FirstStepCreateReview = (props) => { | |||
| formik.setFieldValue("exchangeSucceed", event.target.value.mainText) | |||
| } | |||
| > | |||
| {Object.keys(reviewEnum).map((property) => ( | |||
| {Object.keys(reviewEnum).map((property) => { | |||
| if (property === "NOT_BAD") return; | |||
| return ( | |||
| <SelectOption | |||
| key={reviewEnum[property].value} | |||
| value={reviewEnum[property]} | |||
| > | |||
| {reviewEnum[property].mainText} | |||
| </SelectOption> | |||
| ))} | |||
| )})} | |||
| </SelectField> | |||
| <FieldLabel leftText={t("reviews.comment")} /> | |||
| @@ -4,11 +4,9 @@ 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 { fetchOneChat, setOneChat } from "../../store/actions/chat/chatActions"; | |||
| import { | |||
| // selectLatestChats, | |||
| selectSelectedChat, | |||
| } from "../../store/selectors/chatSelectors"; | |||
| import DirectChatContent from "./DirectChatContent/DirectChatContent"; | |||
| @@ -20,46 +18,43 @@ const DirectChat = () => { | |||
| 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 dispatch = useDispatch(); | |||
| 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 | |||
| } | |||
| 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(); | |||
| }, [chat, location.state, offer]); | |||
| useEffect(() => { | |||
| console.log(location.state) | |||
| if (routeMatch.params.idChat && location.state?.offerId) { | |||
| if (routeMatch.params.idChat) { | |||
| refreshChat(); | |||
| } | |||
| }, [routeMatch.params.idChat, location.state?.offerId]); | |||
| const refreshChat = () => { | |||
| if (routeMatch.params.idChat === "newMessage") { | |||
| dispatch(fetchOneOffer(location.state.offerId)) | |||
| dispatch(fetchOneOffer(location.state.offerId)); | |||
| dispatch(setOneChat({})); | |||
| } else { | |||
| dispatch(fetchOneChat(routeMatch.params.idChat)); | |||
| } | |||
| @@ -32,15 +32,15 @@ const DirectChatHeader = (props) => { | |||
| } | |||
| return false; | |||
| }, [exchange, userId]) | |||
| const refetchExchange = () => { | |||
| dispatch(fetchExchange(chat.chat.exchangeId)); | |||
| } | |||
| const makeReview = () => { | |||
| setShowReviewModal(true); | |||
| }; | |||
| const handleGiveReviewSuccess = () => { | |||
| refetchExchange(); | |||
| } | |||
| const refetchExchange = () => { | |||
| dispatch(fetchExchange(chat.chat.exchangeId)); | |||
| } | |||
| return ( | |||
| <DirectChatHeaderContainer> | |||
| {showReviewModal && ( | |||
| @@ -1,20 +1,24 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { DirectChatHeaderTitleContainer, HeaderTitleContent, MessageIcon } from './DirectChatHeaderTitle.styled' | |||
| import { useTranslation } from 'react-i18next' | |||
| 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(); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <DirectChatHeaderTitleContainer> | |||
| <MessageIcon /> | |||
| <HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent> | |||
| <MessageIcon /> | |||
| <HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent> | |||
| </DirectChatHeaderTitleContainer> | |||
| ) | |||
| } | |||
| ); | |||
| }; | |||
| DirectChatHeaderTitle.propTypes = { | |||
| children: PropTypes.node, | |||
| } | |||
| children: PropTypes.node, | |||
| }; | |||
| export default DirectChatHeaderTitle | |||
| export default DirectChatHeaderTitle; | |||
| @@ -8,7 +8,10 @@ import { | |||
| import { useTranslation } from "react-i18next"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| import { sendMessage, startNewChat } from "../../../store/actions/chat/chatActions"; | |||
| import { | |||
| sendMessage, | |||
| startNewChat, | |||
| } from "../../../store/actions/chat/chatActions"; | |||
| import { useHistory, useLocation } from "react-router-dom"; | |||
| const DirectChatNewMessage = (props) => { | |||
| @@ -35,13 +38,14 @@ const DirectChatNewMessage = (props) => { | |||
| 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})) | |||
| } | |||
| dispatch( | |||
| startNewChat({ offerId, message: typedValue, handleMessageSendSuccess }) | |||
| ); | |||
| }; | |||
| return ( | |||
| <DirectChatNewMessageContainer> | |||
| <NewMessageField | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useEffect, useMemo, useState } from "react"; | |||
| import React, { useEffect, useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { MiniChatColumnContainer } from "./MiniChatColumn.styled"; | |||
| import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard"; | |||
| @@ -14,9 +14,7 @@ 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(); | |||
| @@ -25,29 +23,15 @@ const MiniChatColumn = () => { | |||
| return { | |||
| interlocutorData: { | |||
| image: offer?.companyData?.image, | |||
| name: offer?.companyData?.company?.name | |||
| 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); | |||
| } | |||
| name: offer?.offer?.name, | |||
| }, | |||
| }; | |||
| } | |||
| }, [location.state]) | |||
| useEffect(() => { | |||
| setChatsToShow([...chats]); | |||
| }, [chats]) | |||
| return {}; | |||
| }, [offer, location.state]); | |||
| useEffect(() => { | |||
| dispatch(fetchChats()); | |||
| @@ -55,20 +39,18 @@ const MiniChatColumn = () => { | |||
| return ( | |||
| <MiniChatColumnContainer> | |||
| <MiniChatColumnHeader /> | |||
| {isThereNewChat && ( | |||
| <MiniChatCard | |||
| chat={newChat} | |||
| selected | |||
| /> | |||
| )} | |||
| {chatsToShow.map((item) => { | |||
| {location.state?.offerId && <MiniChatCard chat={newChat} selected />} | |||
| {chats.map((item) => { | |||
| return ( | |||
| <MiniChatCard | |||
| key={Date.now() * Math.random()} | |||
| chat={item} | |||
| selected={item?.chat?._id === selectedChat?.chat?._id && !isThereNewChat} | |||
| /> | |||
| )})} | |||
| <MiniChatCard | |||
| key={Date.now() * Math.random()} | |||
| chat={item} | |||
| selected={ | |||
| item?.chat?._id === selectedChat?.chat?._id | |||
| } | |||
| /> | |||
| ); | |||
| })} | |||
| </MiniChatColumnContainer> | |||
| ); | |||
| }; | |||
| @@ -1,20 +1,26 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { HeaderTitleContent, MailIcon, MiniChatColumnHeaderContainer } from './MiniChatColumnHeaderTitle.styled' | |||
| import { useTranslation } from 'react-i18next' | |||
| 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(); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <MiniChatColumnHeaderContainer> | |||
| <MailIcon/> | |||
| <HeaderTitleContent>{t("messages.miniChatHeaderTitle")}</HeaderTitleContent> | |||
| <MailIcon /> | |||
| <HeaderTitleContent> | |||
| {t("messages.miniChatHeaderTitle")} | |||
| </HeaderTitleContent> | |||
| </MiniChatColumnHeaderContainer> | |||
| ) | |||
| } | |||
| ); | |||
| }; | |||
| MiniChatColumnHeader.propTypes = { | |||
| children: PropTypes.node, | |||
| } | |||
| children: PropTypes.node, | |||
| }; | |||
| export default MiniChatColumnHeader | |||
| export default MiniChatColumnHeader; | |||
| @@ -15,20 +15,16 @@ import PropTypes from "prop-types"; | |||
| const DropdownList = (props) => { | |||
| const [listShown, setListShown] = useState(props.defaultOpen); | |||
| useEffect(() => { | |||
| if (props.open !== null || props.open !== undefined) { | |||
| if (props.open !== null || props.open !== undefined) | |||
| setListShown(props.open); | |||
| } | |||
| }, [props.open]); | |||
| const handleShow = () => { | |||
| if (props.setIsOpened) { | |||
| if (props.setIsOpened) | |||
| props.setIsOpened(!listShown); | |||
| } | |||
| if (!props.disabled) { | |||
| if (!props.disabled) | |||
| setListShown((prevState) => !prevState); | |||
| if (!(props.open !== null || props.open !== undefined)) | |||
| setListShown((prevState) => !prevState); | |||
| } | |||
| if (!(props.open !== null || props.open !== undefined)) { | |||
| setListShown(prevState => !prevState) | |||
| } | |||
| }; | |||
| return ( | |||
| <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | |||
| @@ -48,7 +44,11 @@ const DropdownList = (props) => { | |||
| > | |||
| {props.title} | |||
| </DropdownTitle> | |||
| {(props.open !== null && props.open !== undefined ? props.open : listShown) ? ( | |||
| {( | |||
| props.open !== null && props.open !== undefined | |||
| ? props.open | |||
| : listShown | |||
| ) ? ( | |||
| <ToggleIconOpened | |||
| style={props.toggleIconStyles} | |||
| onClick={!props.disabled ? () => handleShow() : () => {}} | |||
| @@ -65,16 +65,20 @@ const DropdownList = (props) => { | |||
| </ToggleIconClosed> | |||
| )} | |||
| </DropdownHeader> | |||
| <ToggleContainer shouldShow={props.open !== null && props.open !== undefined ? props.open : listShown}> | |||
| <ToggleContainer | |||
| shouldShow={ | |||
| props.open !== null && props.open !== undefined | |||
| ? props.open | |||
| : listShown | |||
| } | |||
| > | |||
| <DropdownOptions>{props.headerOptions}</DropdownOptions> | |||
| <ListContainer>{props.children}</ListContainer> | |||
| </ToggleContainer> | |||
| </DropdownListContainer> | |||
| ); | |||
| }; | |||
| export default DropdownList; | |||
| DropdownList.propTypes = { | |||
| title: PropTypes.string, | |||
| dropdownIcon: PropTypes.node, | |||
| @@ -90,7 +94,6 @@ DropdownList.propTypes = { | |||
| open: PropTypes.bool, | |||
| disabled: PropTypes.bool, | |||
| }; | |||
| DropdownList.defaultProps = { | |||
| fullWidth: false, | |||
| defaultOpen: false, | |||
| @@ -21,7 +21,7 @@ import { | |||
| ToolsContainer, | |||
| UserIcon, | |||
| } from "./Drawer.styled"; | |||
| import { useSelector } from "react-redux"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| @@ -29,12 +29,14 @@ 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"; | |||
| import { logoutUser } from "../../../store/actions/login/loginActions"; | |||
| export const Drawer = (props) => { | |||
| const user = useSelector(selectUserId); | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| const name = useSelector(selectProfileName); | |||
| const dispatch = useDispatch(); | |||
| const goToMyPosts = () => { | |||
| props.toggleDrawer(); | |||
| @@ -60,7 +62,10 @@ export const Drawer = (props) => { | |||
| props.toggleDrawer(); | |||
| props.addOffer(); | |||
| } | |||
| const logoutUser = () => {}; | |||
| const logout = () => { | |||
| props.toggleDrawer(); | |||
| dispatch(logoutUser()); | |||
| }; | |||
| return ( | |||
| <DrawerContainer> | |||
| <CloseButton onClick={props.toggleDrawer}> | |||
| @@ -101,7 +106,7 @@ export const Drawer = (props) => { | |||
| {t("header.addOffer")} | |||
| </AddOfferButton> | |||
| <LogoutButton> | |||
| <IconButton onClick={logoutUser}> | |||
| <IconButton onClick={logout}> | |||
| <LogoutIcon /> | |||
| </IconButton> | |||
| <LogoutText>{t("common.logout")}</LogoutText> | |||
| @@ -183,7 +183,6 @@ const Header = (props) => { | |||
| searchRef.current.removeEventListener("keyup", listener); | |||
| }; | |||
| const handleSearch = (value) => { | |||
| if (value.length === 0) return; | |||
| search.searchOffers(value); | |||
| }; | |||
| const toggleFilters = () => { | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useEffect, useRef, useState } from "react"; | |||
| import React, { useCallback, useEffect, useRef, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| AddFile, | |||
| @@ -12,8 +12,6 @@ import { IconButton } from "../Buttons/IconButton/IconButton"; | |||
| import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg"; | |||
| import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg"; | |||
| // import { Input } from "@mui/material"; | |||
| const ImagePicker = (props) => { | |||
| const fileInputRef = useRef(null); | |||
| const imageRef = useRef(null); | |||
| @@ -21,27 +19,23 @@ const ImagePicker = (props) => { | |||
| const [isEditing, setIsEditing] = useState(false); | |||
| useEffect(() => { | |||
| console.log("image", props); | |||
| if (props.image) { | |||
| if (props.image) | |||
| setImage(props.image); | |||
| } | |||
| }, [props.image]); | |||
| let listener; | |||
| useEffect(() => { | |||
| listener = (event) => { | |||
| if (imageRef.current) { | |||
| if (imageRef.current.contains(event.target)) { | |||
| setIsEditing(true); | |||
| } else { | |||
| setIsEditing(false); | |||
| } | |||
| let listener = useCallback((event) => { | |||
| if (imageRef.current) { | |||
| if (imageRef.current.contains(event.target)) { | |||
| setIsEditing(true); | |||
| } else { | |||
| setIsEditing(false); | |||
| } | |||
| }; | |||
| } | |||
| }, [imageRef.current]) | |||
| useEffect(() => { | |||
| window.addEventListener("click", listener); | |||
| return () => window.removeEventListener("click", listener); | |||
| }, [imageRef]); | |||
| }, []); | |||
| const handleChange = () => { | |||
| fileInputRef.current.value = ""; | |||
| fileInputRef.current.click(); | |||
| @@ -57,7 +51,6 @@ const ImagePicker = (props) => { | |||
| console.log(error); | |||
| }; | |||
| }; | |||
| const handleDelete = () => { | |||
| if (props.deleteImage) props.deleteImage(); | |||
| setImage(""); | |||
| @@ -96,7 +89,6 @@ const ImagePicker = (props) => { | |||
| </ImagePickerContainer> | |||
| ); | |||
| }; | |||
| ImagePicker.propTypes = { | |||
| children: PropTypes.node, | |||
| className: PropTypes.string, | |||
| @@ -105,5 +97,4 @@ ImagePicker.propTypes = { | |||
| deleteImage: PropTypes.func, | |||
| showDeleteIcon: PropTypes.bool, | |||
| }; | |||
| export default ImagePicker; | |||
| @@ -1,187 +0,0 @@ | |||
| import React, { useEffect, useState, useRef } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ErrorMessage } from 'formik'; | |||
| import IconButton from '../IconButton/IconButton'; | |||
| import { ReactComponent as Search } from '../../assets/images/svg/search.svg'; | |||
| import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg'; | |||
| import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg'; | |||
| import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg'; | |||
| const BaseInputField = ({ | |||
| type, | |||
| label, | |||
| field, | |||
| form, | |||
| placeholder, | |||
| clearPlaceholderOnFocus = true, | |||
| isSearch, | |||
| className, | |||
| disabled, | |||
| centerText, | |||
| link, | |||
| errorMessage, | |||
| autoFocus, | |||
| isCapsLockOn, | |||
| ...props | |||
| }) => { | |||
| const [inputPlaceholder, setPlaceholder] = useState(placeholder); | |||
| const inputField = useRef(null); | |||
| useEffect(() => { | |||
| if (autoFocus) { | |||
| inputField.current.focus(); | |||
| } | |||
| }, [autoFocus, inputField]); | |||
| useEffect(() => { | |||
| if (errorMessage) { | |||
| form.setFieldError(field.name, errorMessage); | |||
| } | |||
| }, [errorMessage]); // eslint-disable-line | |||
| useEffect(() => { | |||
| setPlaceholder(placeholder); | |||
| }, [placeholder]); | |||
| const [inputType, setInputType] = useState('password'); | |||
| const passwordInput = type === 'password' ? ' c-input--password' : ''; | |||
| const showPassword = () => { | |||
| if (inputType === 'password') { | |||
| setInputType('text'); | |||
| } else { | |||
| setInputType('password'); | |||
| } | |||
| }; | |||
| // Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this | |||
| // If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels? | |||
| const fieldName = field.name.split('.'); | |||
| const formError = | |||
| fieldName[0] && fieldName[1] | |||
| ? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]] | |||
| : form.errors[fieldName[0]]; | |||
| const formTouched = | |||
| fieldName[0] && fieldName[1] | |||
| ? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]] | |||
| : form.touched[fieldName[0]]; | |||
| function styles() { | |||
| let style = 'c-input'; | |||
| if (formError && formTouched) { | |||
| style += ` c-input--error`; | |||
| } | |||
| if (type === 'password') { | |||
| style += ` c-input--password`; | |||
| } | |||
| if (isSearch) { | |||
| style += ` c-input--search`; | |||
| } | |||
| if (centerText) { | |||
| style += ` c-input--center-text`; | |||
| } | |||
| if (type === 'number') { | |||
| style += ` c-input--demi-bold`; | |||
| } | |||
| if (className) { | |||
| style += ` ${className}`; | |||
| } | |||
| return style; | |||
| } | |||
| const additionalActions = () => { | |||
| if (!clearPlaceholderOnFocus) { | |||
| return null; | |||
| } | |||
| return { | |||
| onFocus: () => { | |||
| setPlaceholder(''); | |||
| }, | |||
| onBlur: (e) => { | |||
| setPlaceholder(placeholder); | |||
| field.onBlur(e); | |||
| }, | |||
| }; | |||
| }; | |||
| return ( | |||
| <div className={styles()}> | |||
| {!!label && ( | |||
| <label className="c-input__label" htmlFor={field.name}> | |||
| {label} | |||
| </label> | |||
| )} | |||
| {link && <div className="c-input__link">{link}</div>} | |||
| <div className="c-input__field-wrap"> | |||
| <input | |||
| ref={inputField} | |||
| type={type === 'password' ? inputType : type} | |||
| placeholder={inputPlaceholder} | |||
| disabled={disabled} | |||
| {...field} | |||
| {...props} | |||
| {...additionalActions()} | |||
| className="c-input__field" | |||
| /> | |||
| {!!isSearch && <Search className="c-input__icon" />} | |||
| {!!passwordInput && ( | |||
| <> | |||
| {isCapsLockOn && <CapsLock className="c-input__caps-lock" />} | |||
| <IconButton | |||
| onClick={() => { | |||
| showPassword(); | |||
| }} | |||
| className="c-input__icon" | |||
| > | |||
| {inputType === 'password' ? <EyeOff /> : <EyeOn />} | |||
| </IconButton> | |||
| </> | |||
| )} | |||
| </div> | |||
| <ErrorMessage name={field.name}> | |||
| {(errorMessage) => ( | |||
| <span className="c-input__error">{errorMessage}</span> | |||
| )} | |||
| </ErrorMessage> | |||
| </div> | |||
| ); | |||
| }; | |||
| BaseInputField.propTypes = { | |||
| type: PropTypes.string, | |||
| field: PropTypes.shape({ | |||
| name: PropTypes.string, | |||
| onFocus: PropTypes.func, | |||
| onBlur: PropTypes.func, | |||
| }), | |||
| form: PropTypes.shape({ | |||
| errors: PropTypes.shape({}), | |||
| setFieldError: PropTypes.func, | |||
| touched: PropTypes.shape({}), | |||
| }), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
| disabled: PropTypes.bool, | |||
| isSearch: PropTypes.bool, | |||
| className: PropTypes.string, | |||
| link: PropTypes.node, | |||
| errorMessage: PropTypes.string, | |||
| centerText: PropTypes.bool, | |||
| clearPlaceholderOnFocus: PropTypes.bool, | |||
| demiBold: PropTypes.bool, | |||
| touched: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| isCapsLockOn: PropTypes.bool, | |||
| }; | |||
| export default BaseInputField; | |||
| @@ -1,40 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ReactComponent as Checked } from '../../assets/images/svg/checked.svg'; | |||
| import { ReactComponent as Unchecked } from '../../assets/images/svg/unchecked.svg'; | |||
| const Checkbox = ({ className, children, name, onChange, checked, field }) => ( | |||
| <label htmlFor={name} className={`c-checkbox ${className || ''}`}> | |||
| <input | |||
| name={name} | |||
| id={name} | |||
| className="c-checkbox__field" | |||
| type="checkbox" | |||
| checked={checked} | |||
| {...field} | |||
| onChange={onChange || field.onChange} | |||
| /> | |||
| <div className="c-checkbox__indicator"> | |||
| {checked ? ( | |||
| <Checked className="c-checkbox__icon" /> | |||
| ) : ( | |||
| <Unchecked className="c-checkbox__icon" /> | |||
| )} | |||
| </div> | |||
| <div className="c-checkbox__text">{children}</div> | |||
| </label> | |||
| ); | |||
| Checkbox.propTypes = { | |||
| children: PropTypes.node, | |||
| onChange: PropTypes.func, | |||
| checked: PropTypes.bool, | |||
| name: PropTypes.string, | |||
| field: PropTypes.shape({ | |||
| onChange: PropTypes.func, | |||
| }), | |||
| className: PropTypes.string, | |||
| }; | |||
| export default Checkbox; | |||
| @@ -1,123 +0,0 @@ | |||
| import React, { useEffect, useRef } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ErrorMessage, useField } from 'formik'; | |||
| import CurrencyInput from 'react-currency-input-field'; | |||
| import { formatMoneyNumeral } from '../../util/helpers/numeralHelpers'; | |||
| import { | |||
| PLUS_SYMBOL, | |||
| MINUS_SYMBOL, | |||
| NUMPAD_MINUS_SYMBOL, | |||
| NUMPAD_PLUS_SYMBOL, | |||
| K_KEYCODE, | |||
| } from '../../constants/keyCodeConstants'; | |||
| const CurrencyField = ({ | |||
| autoFocus, | |||
| notCentered, | |||
| notBold, | |||
| label, | |||
| onChange, | |||
| value, | |||
| ...props | |||
| }) => { | |||
| const [field, meta] = useField(props); | |||
| const inputField = useRef(null); | |||
| function styles() { | |||
| let style = 'c-currency-field'; | |||
| if (meta.error && meta.touched) { | |||
| style += ` c-currency-field--error`; | |||
| } | |||
| if (notCentered) { | |||
| style += ` c-currency-field--not-centered`; | |||
| } | |||
| if (notBold) { | |||
| style += ` c-currency-field--not-bold`; | |||
| } | |||
| return style; | |||
| } | |||
| useEffect(() => { | |||
| if (autoFocus) { | |||
| inputField.current.focus(); | |||
| } | |||
| }, [autoFocus, inputField]); | |||
| const onKeydownHandler = (event) => { | |||
| if ( | |||
| event.keyCode === MINUS_SYMBOL || | |||
| event.keyCode === PLUS_SYMBOL || | |||
| event.keyCode === NUMPAD_MINUS_SYMBOL || | |||
| event.keyCode === NUMPAD_PLUS_SYMBOL || | |||
| event.keyCode === K_KEYCODE | |||
| ) { | |||
| event.preventDefault(); | |||
| } | |||
| }; | |||
| const prefix = formatMoneyNumeral(0); | |||
| const prefixSymbol = () => { | |||
| if (prefix.includes('CAD')) { | |||
| return 'CAD '; | |||
| } | |||
| return '$'; | |||
| }; | |||
| return ( | |||
| <div className={styles()}> | |||
| {!!label && ( | |||
| <label className="c-currency-field__label" htmlFor={field.name}> | |||
| {label} | |||
| </label> | |||
| )} | |||
| {value ? ( | |||
| <CurrencyInput | |||
| {...props} | |||
| prefix={prefixSymbol()} | |||
| onValueChange={(value) => { | |||
| onChange(value ? Number(value) : ''); | |||
| }} | |||
| onKeyDown={(event) => onKeydownHandler(event)} | |||
| ref={inputField} | |||
| defaultValue={0} | |||
| value={value} | |||
| /> | |||
| ) : ( | |||
| <CurrencyInput | |||
| {...props} | |||
| prefix={prefixSymbol()} | |||
| onValueChange={(value) => { | |||
| onChange(value ? Number(value) : ''); | |||
| }} | |||
| onKeyDown={(event) => onKeydownHandler(event)} | |||
| ref={inputField} | |||
| /> | |||
| )} | |||
| <ErrorMessage name={field.name}> | |||
| {(errorMessage) => ( | |||
| <span className="c-currency-field__error">{errorMessage}</span> | |||
| )} | |||
| </ErrorMessage> | |||
| </div> | |||
| ); | |||
| }; | |||
| CurrencyField.propTypes = { | |||
| field: PropTypes.shape({ | |||
| name: PropTypes.string, | |||
| }), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| disabled: PropTypes.bool, | |||
| onChange: PropTypes.func, | |||
| autoFocus: PropTypes.bool, | |||
| notCentered: PropTypes.bool, | |||
| notBold: PropTypes.bool, | |||
| value: PropTypes.number, | |||
| }; | |||
| export default CurrencyField; | |||
| @@ -1,33 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| const EmailField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| ...props | |||
| }) => ( | |||
| <BaseInputField | |||
| type="email" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| {...props} | |||
| /> | |||
| ); | |||
| EmailField.propTypes = { | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| }; | |||
| export default EmailField; | |||
| @@ -1,74 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| import { | |||
| PERIOD_SYMBOL, | |||
| COMMA_SYMBOL, | |||
| PLUS_SYMBOL, | |||
| MINUS_SYMBOL, | |||
| NUMPAD_PERIOD_SYMBOL, | |||
| NUMPAD_MINUS_SYMBOL, | |||
| NUMPAD_PLUS_SYMBOL, | |||
| DOWN_ARROW_KEYCODE, | |||
| UP_ARROW_KEYCODE, | |||
| } from '../../constants/keyCodeConstants'; | |||
| const NumberField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| preventAllExceptNumbers, | |||
| ...props | |||
| }) => { | |||
| const onKeydownHandler = (event) => { | |||
| if (preventAllExceptNumbers) { | |||
| if ( | |||
| event.keyCode === PERIOD_SYMBOL || | |||
| event.keyCode === COMMA_SYMBOL || | |||
| event.keyCode === NUMPAD_PERIOD_SYMBOL || | |||
| event.keyCode === DOWN_ARROW_KEYCODE || | |||
| event.keyCode === UP_ARROW_KEYCODE | |||
| ) { | |||
| event.preventDefault(); | |||
| } | |||
| } | |||
| if ( | |||
| event.keyCode === PLUS_SYMBOL || | |||
| event.keyCode === MINUS_SYMBOL || | |||
| event.keyCode === NUMPAD_MINUS_SYMBOL || | |||
| event.keyCode === NUMPAD_PLUS_SYMBOL || | |||
| event.keyCode === DOWN_ARROW_KEYCODE || | |||
| event.keyCode === UP_ARROW_KEYCODE | |||
| ) { | |||
| event.preventDefault(); | |||
| } | |||
| }; | |||
| return ( | |||
| <BaseInputField | |||
| type="number" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| {...props} | |||
| onKeyDown={(event) => onKeydownHandler(event)} | |||
| /> | |||
| ); | |||
| }; | |||
| NumberField.propTypes = { | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | |||
| disabled: PropTypes.bool, | |||
| preventAllExceptNumbers: PropTypes.bool, | |||
| }; | |||
| export default NumberField; | |||
| @@ -1,74 +0,0 @@ | |||
| import React, { useState } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| import PasswordStrength from './PasswordStrength'; | |||
| const PasswordField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| shouldTestPasswordStrength, | |||
| autoFocus, | |||
| ...props | |||
| }) => { | |||
| const [passwordValue, setPasswordValue] = useState(''); | |||
| const [isCapsLockOn, setIsCapsLockOn] = useState(false); | |||
| const onChange = (e) => { | |||
| if (shouldTestPasswordStrength) { | |||
| const { value } = e.target; | |||
| setPasswordValue(value); | |||
| } | |||
| field.onChange(e); | |||
| }; | |||
| const onKeyDown = (keyEvent) => { | |||
| if (keyEvent.getModifierState('CapsLock')) { | |||
| setIsCapsLockOn(true); | |||
| } else { | |||
| setIsCapsLockOn(false); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className="c-password"> | |||
| <BaseInputField | |||
| type="password" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| {...props} | |||
| onChange={onChange} | |||
| autoFocus={autoFocus} | |||
| onKeyDown={onKeyDown} | |||
| isCapsLockOn={isCapsLockOn} | |||
| /> | |||
| {shouldTestPasswordStrength && ( | |||
| <PasswordStrength | |||
| passwordValue={passwordValue} | |||
| shouldTestPasswordStrength | |||
| /> | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| PasswordField.propTypes = { | |||
| field: PropTypes.shape({ | |||
| onChange: PropTypes.func, | |||
| }), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| shouldTestPasswordStrength: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| }; | |||
| export default PasswordField; | |||
| @@ -1,130 +0,0 @@ | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import owasp from 'owasp-password-strength-test'; | |||
| import i18next from 'i18next'; | |||
| owasp.config({ | |||
| minOptionalTestsToPass: 3, | |||
| }); | |||
| const passwordStrengthOptions = [ | |||
| { | |||
| strength: 'weak', | |||
| color: '#FF5028', | |||
| }, | |||
| { | |||
| strength: 'average', | |||
| color: '#FDB942', | |||
| }, | |||
| { | |||
| strength: 'good', | |||
| color: '#06BEE7', | |||
| }, | |||
| { | |||
| strength: 'strong', | |||
| color: '#00876A', | |||
| }, | |||
| ]; | |||
| /** | |||
| * User must pass a required test and at least 3 optional. | |||
| * @param result - owasp result | |||
| * @returns {number} - index of password strength 0-3 | |||
| */ | |||
| function getPasswordStrengthIndex(result) { | |||
| // requirement for strong password is required test passed and at least 3 optional tests | |||
| if (result.strong) { | |||
| return 3; | |||
| } | |||
| if (!result.strong && result.optionalTestsPassed >= 3) { | |||
| return 2; | |||
| } | |||
| if (result.optionalTestsPassed <= 0) { | |||
| return 0; | |||
| } | |||
| return result.optionalTestsPassed - 1; | |||
| } | |||
| const PasswordStrength = ({ | |||
| shouldTestPasswordStrength, | |||
| passwordValue, | |||
| passwordStrengthTestsRequired, | |||
| }) => { | |||
| const strengthContainer = useRef(null); | |||
| const [passwordStrength, setPasswordStrength] = useState({ | |||
| width: 0, | |||
| color: 'red', | |||
| }); | |||
| const [error, setError] = useState(''); | |||
| useEffect(() => { | |||
| if (shouldTestPasswordStrength && passwordValue) { | |||
| const bBox = strengthContainer.current.getBoundingClientRect(); | |||
| const result = owasp.test(passwordValue); | |||
| const passwordStrengthIndex = getPasswordStrengthIndex(result); | |||
| const passwordOption = passwordStrengthOptions[passwordStrengthIndex]; | |||
| const width = !passwordValue | |||
| ? 0 | |||
| : (bBox.width * (passwordStrengthIndex + 1)) / | |||
| passwordStrengthTestsRequired; | |||
| setPasswordStrength({ width, color: passwordOption.color }); | |||
| const strength = i18next.t(`password.${passwordOption.strength}`); | |||
| setError(i18next.t('login.passwordStrength', { strength })); | |||
| } | |||
| }, [ | |||
| passwordValue, | |||
| shouldTestPasswordStrength, | |||
| passwordStrengthTestsRequired, | |||
| ]); | |||
| if (!shouldTestPasswordStrength || !passwordValue) { | |||
| return null; | |||
| } | |||
| const renderError = () => { | |||
| if (!error) { | |||
| return null; | |||
| } | |||
| return ( | |||
| <div | |||
| className="c-input--error" | |||
| style={{ | |||
| color: passwordStrength.color, | |||
| }} | |||
| > | |||
| {error} | |||
| </div> | |||
| ); | |||
| }; | |||
| return ( | |||
| <div ref={strengthContainer} className="c-password-strength__container"> | |||
| <div className="c-password-strength__line--wrapper"> | |||
| <div | |||
| className="c-password-strength__line" | |||
| style={{ | |||
| backgroundColor: passwordStrength.color, | |||
| width: passwordStrength.width, | |||
| }} | |||
| /> | |||
| </div> | |||
| {renderError()} | |||
| </div> | |||
| ); | |||
| }; | |||
| PasswordStrength.propTypes = { | |||
| shouldTestPasswordStrength: PropTypes.bool, | |||
| passwordValue: PropTypes.string, | |||
| passwordStrengthTestsRequired: PropTypes.number, | |||
| }; | |||
| PasswordStrength.defaultProps = { | |||
| passwordStrengthTestsRequired: 4, | |||
| }; | |||
| export default PasswordStrength; | |||
| @@ -1,45 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import NumberFormat from 'react-number-format'; | |||
| import TextField from './TextField'; | |||
| const PercentageField = ({ field, ...props }) => { | |||
| const handleOnChange = (percentageField) => { | |||
| const { floatValue } = percentageField; | |||
| if (!props.onChange) { | |||
| throw Error('Provide an onChange handler'); | |||
| } | |||
| if (floatValue > 100) { | |||
| return props.onChange('100'); | |||
| } | |||
| if (floatValue <= 0 || !floatValue) { | |||
| return props.onChange('0'); | |||
| } | |||
| return props.onChange(floatValue.toString()); | |||
| }; | |||
| return ( | |||
| <NumberFormat | |||
| format="###%" | |||
| value={field.value} | |||
| customInput={TextField} | |||
| field={field} | |||
| {...props} | |||
| onValueChange={handleOnChange} | |||
| onChange={() => {}} | |||
| /> | |||
| ); | |||
| }; | |||
| PercentageField.propTypes = { | |||
| onChange: PropTypes.func, | |||
| field: PropTypes.shape({ | |||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
| }), | |||
| }; | |||
| export default PercentageField; | |||
| @@ -1,49 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ErrorMessage, useField } from 'formik'; | |||
| import PhoneInput from 'react-phone-number-input'; | |||
| import 'react-phone-number-input/style.css'; | |||
| const PhoneNumberField = ({ label, ...props }) => { | |||
| const [field, meta] = useField(props); | |||
| const inputErrorClassName = | |||
| meta.error && meta.touched ? 'c-input--error' : ''; | |||
| return ( | |||
| <div className={`c-input c-phone-number ${inputErrorClassName}`}> | |||
| {!!label && ( | |||
| <label className="c-input__label" htmlFor={field.name}> | |||
| {label} | |||
| </label> | |||
| )} | |||
| <PhoneInput | |||
| international | |||
| defaultCountry="US" | |||
| {...field} | |||
| {...props} | |||
| onChange={(value) => { | |||
| props.onPhoneChange(value); | |||
| }} | |||
| countryOptionsOrder={['US']} | |||
| /> | |||
| <ErrorMessage name={field.name}> | |||
| {(errorMessage) => ( | |||
| <span className="c-input__error">{errorMessage}</span> | |||
| )} | |||
| </ErrorMessage> | |||
| </div> | |||
| ); | |||
| }; | |||
| PhoneNumberField.propTypes = { | |||
| field: PropTypes.shape({ | |||
| name: PropTypes.string, | |||
| }), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| disabled: PropTypes.bool, | |||
| onChange: PropTypes.func, | |||
| onPhoneChange: PropTypes.func, | |||
| }; | |||
| export default PhoneNumberField; | |||
| @@ -1,54 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ReactComponent as RadioOn } from '../../assets/images/svg/radio-on.svg'; | |||
| import { ReactComponent as RadioOff } from '../../assets/images/svg/radio-off.svg'; | |||
| const Checkbox = ({ | |||
| className, | |||
| children, | |||
| name, | |||
| checked, | |||
| field, | |||
| value, | |||
| selected, | |||
| id, | |||
| }) => ( | |||
| <label | |||
| htmlFor={name} | |||
| className={`c-radio ${selected ? 'c-radio--selected' : ''} ${ | |||
| className || '' | |||
| }`} | |||
| > | |||
| <input | |||
| name={name} | |||
| id={id} | |||
| className="c-radio__field" | |||
| type="radio" | |||
| checked={checked} | |||
| value={value} | |||
| {...field} | |||
| /> | |||
| <div className="c-radio__indicator"> | |||
| {selected ? ( | |||
| <RadioOn className="c-radio__icon" /> | |||
| ) : ( | |||
| <RadioOff className="c-radio__icon" /> | |||
| )} | |||
| </div> | |||
| <div className="c-radio__text">{children}</div> | |||
| </label> | |||
| ); | |||
| Checkbox.propTypes = { | |||
| children: PropTypes.node, | |||
| checked: PropTypes.bool, | |||
| name: PropTypes.string, | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| className: PropTypes.string, | |||
| value: PropTypes.string, | |||
| selected: PropTypes.bool, | |||
| id: PropTypes.string, | |||
| }; | |||
| export default Checkbox; | |||
| @@ -1,37 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| const Search = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| className, | |||
| ...props | |||
| }) => ( | |||
| <BaseInputField | |||
| type="text" | |||
| label="" | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| isSearch | |||
| className={className} | |||
| {...props} | |||
| /> | |||
| ); | |||
| Search.propTypes = { | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| className: PropTypes.string, | |||
| }; | |||
| export default Search; | |||
| @@ -1,122 +0,0 @@ | |||
| import React, { useEffect } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import Select, { components, createFilter } from 'react-select'; | |||
| import { ErrorMessage, useField } from 'formik'; | |||
| import { ReactComponent as FilledChevronDown } from '../../assets/images/svg/filled-chevron-down.svg'; | |||
| const SelectField = ({ | |||
| label, | |||
| disabled, | |||
| options, | |||
| link, | |||
| defaultSelected = null, | |||
| dropdownFullHeight, | |||
| selectOption, | |||
| ...props | |||
| }) => { | |||
| const [field, meta, helpers] = useField(props); | |||
| const filterConfig = { | |||
| ignoreCase: true, | |||
| ignoreAccents: true, | |||
| trim: true, | |||
| matchFrom: 'start', | |||
| }; | |||
| useEffect(() => { | |||
| if (defaultSelected) { | |||
| helpers.setValue(defaultSelected); | |||
| } | |||
| }, [defaultSelected]); // eslint-disable-line | |||
| const DropdownIndicator = (props) => | |||
| components.DropdownIndicator && ( | |||
| <components.DropdownIndicator {...props}> | |||
| <FilledChevronDown /> | |||
| </components.DropdownIndicator> | |||
| ); | |||
| function styles() { | |||
| let style = 'c-input'; | |||
| if (meta.error && meta.touched) { | |||
| style += ` c-input--error`; | |||
| } | |||
| if (dropdownFullHeight) { | |||
| style += ` c-input--dropdown-full-height`; | |||
| } | |||
| return style; | |||
| } | |||
| return ( | |||
| <div className={styles()}> | |||
| {!!label && ( | |||
| <label className="c-input__label" htmlFor={field.name}> | |||
| {label} | |||
| </label> | |||
| )} | |||
| {!!link && <div className="c-input__link">{link}</div>} | |||
| <Select | |||
| defaultValue={defaultSelected || options[0]} | |||
| components={{ DropdownIndicator }} | |||
| isSearchable={false} | |||
| classNamePrefix="c-select" | |||
| options={options} | |||
| isDisabled={disabled} | |||
| {...field} | |||
| {...props} | |||
| onBlur={(e) => { | |||
| helpers.setTouched(true); | |||
| field.onBlur(e); | |||
| }} | |||
| onChange={(selectedOption) => { | |||
| helpers.setValue(selectedOption); | |||
| if (props.onChange) { | |||
| props.onChange(); | |||
| } | |||
| if (selectOption) { | |||
| selectOption(selectedOption); | |||
| } | |||
| }} | |||
| filterOption={createFilter(filterConfig)} | |||
| /> | |||
| <ErrorMessage name={field.name}> | |||
| {(errorMessage) => { | |||
| if (typeof errorMessage === 'string') { | |||
| return <span className="c-input__error">{errorMessage}</span>; | |||
| } | |||
| return <span className="c-input__error">{errorMessage.value}</span>; | |||
| }} | |||
| </ErrorMessage> | |||
| </div> | |||
| ); | |||
| }; | |||
| SelectField.propTypes = { | |||
| field: PropTypes.shape({ | |||
| name: PropTypes.string, | |||
| }), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| disabled: PropTypes.bool, | |||
| options: PropTypes.arrayOf( | |||
| PropTypes.shape({ | |||
| label: PropTypes.string, | |||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
| }), | |||
| ), | |||
| onChange: PropTypes.func, | |||
| link: PropTypes.node, | |||
| defaultSelected: PropTypes.shape({ | |||
| label: PropTypes.string, | |||
| value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
| }), | |||
| dropdownFullHeight: PropTypes.bool, | |||
| selectOption: PropTypes.func, | |||
| }; | |||
| export default SelectField; | |||
| @@ -1,72 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| import { | |||
| BACKSPACE_KEYCODE, | |||
| TAB_KEYCODE, | |||
| RIGHT_ARROW_KEYCODE, | |||
| LEFT_ARROW_KEYCODE, | |||
| } from '../../constants/keyCodeConstants'; | |||
| const TextField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| centerText, | |||
| autoFocus, | |||
| preventAllExceptNumbers, | |||
| ...props | |||
| }) => { | |||
| const onKeydownHandler = (event) => { | |||
| if (preventAllExceptNumbers) { | |||
| if ( | |||
| event.keyCode === BACKSPACE_KEYCODE || | |||
| event.keyCode === TAB_KEYCODE || | |||
| event.keyCode === RIGHT_ARROW_KEYCODE || | |||
| event.keyCode === LEFT_ARROW_KEYCODE | |||
| ) { | |||
| return; | |||
| } | |||
| if ( | |||
| (event.keyCode < 58 && event.keyCode > 47) || | |||
| (event.keyCode < 106 && event.keyCode > 95) | |||
| ) { | |||
| return; | |||
| } | |||
| event.preventDefault(); | |||
| } | |||
| }; | |||
| return ( | |||
| <BaseInputField | |||
| autoFocus={autoFocus} | |||
| type="text" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| centerText={centerText} | |||
| {...props} | |||
| onKeyDown={(event) => onKeydownHandler(event)} | |||
| /> | |||
| ); | |||
| }; | |||
| TextField.propTypes = { | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.string, | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| centerText: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| preventAllExceptNumbers: PropTypes.bool, | |||
| }; | |||
| export default TextField; | |||
| @@ -1,7 +1,6 @@ | |||
| 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 { useTranslation } from "react-i18next"; | |||
| @@ -28,7 +27,8 @@ const Header = (props) => { | |||
| > | |||
| <ButtonContainer> | |||
| <ArrowButton side={"left"}></ArrowButton> | |||
| <HeaderText>{t("profile.back")}</HeaderText> | |||
| {/* <HeaderText>{t("profile.back")}</HeaderText> */} | |||
| <HeaderText>{t("itemDetailsCard.headerTitle")}</HeaderText> | |||
| </ButtonContainer> | |||
| </HeaderContainer> | |||
| ); | |||
| @@ -6,12 +6,13 @@ 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'; | |||
| const ItemDetails = () => { | |||
| const offer = useSelector(selectOffer); | |||
| const userId = useSelector(selectUserId); | |||
| let isMyProfile = useMemo(() => { | |||
| if (offer?.offer?.userId?.toString() === userId.toString()) { | |||
| if (offer?.offer?.userId?.toString() === userId?.toString()) { | |||
| return true; | |||
| } | |||
| return false; | |||
| @@ -1,49 +1,30 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| DetailIcon, | |||
| DetailText, | |||
| MessageIcon, | |||
| OfferDetails, | |||
| OfferImage, | |||
| OfferTitle, | |||
| DetailContainer, | |||
| HeaderTop, | |||
| HeaderDetails, | |||
| BottomDetails, | |||
| StatusText, | |||
| PIBIcon, | |||
| UserIcon, | |||
| UserIconContainer, | |||
| } from "./ItemDetailsHeaderCard.styled"; | |||
| import { ItemDetailsHeaderContainer } from "./ItemDetailsHeaderCard.styled"; | |||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | |||
| 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"; | |||
| import StatisticDetails from "./StatisticDetails/StatisticDetails"; | |||
| import PIBDetail from "./OfferDetail/PIB/PIBDetail"; | |||
| import CategoryDetail from "./OfferDetail/Category/CategoryDetail"; | |||
| 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>; | |||
| } | |||
| let percentOfSucceededExchanges; | |||
| if (offer?.companyData?.statistics?.exchanges?.succeeded === 0) { | |||
| percentOfSucceededExchanges = 0; | |||
| } else { | |||
| percentOfSucceededExchanges = Math.ceil( | |||
| (offer?.companyData?.statistics?.exchanges?.total / | |||
| offer?.companyData?.statistics?.exchanges?.succeeded) * | |||
| 100 | |||
| ); | |||
| } | |||
| const handleGoProfile = () => { | |||
| history.push(`/profile/${offer?.offer?.userId}`); | |||
| }; | |||
| @@ -72,26 +53,8 @@ const ItemDetailsHeaderCard = (props) => { | |||
| <OfferTitle isMyProfile={props.isMyProfile} onClick={handleGoProfile}> | |||
| {offer?.companyData?.company?.name} | |||
| </OfferTitle> | |||
| <DetailContainer> | |||
| <PIBIcon color={selectedTheme.iconStrokeColor} component="span"> | |||
| <PIB /> | |||
| </PIBIcon> | |||
| <DetailText isMyProfile={props.isMyProfile}> | |||
| PIB - {offer?.companyData?.company?.PIB} | |||
| </DetailText> | |||
| </DetailContainer> | |||
| <DetailContainer shouldHideResponsive> | |||
| <DetailIcon | |||
| color={selectedTheme.iconStrokeColor} | |||
| component="span" | |||
| size="22px" | |||
| > | |||
| <Category width={"22px"} /> | |||
| </DetailIcon> | |||
| <DetailText isMyProfile={props.isMyProfile}> | |||
| {offer?.companyData?.company?.contacts?.location} | |||
| </DetailText> | |||
| </DetailContainer> | |||
| <PIBDetail offer={props.offer} isMyProfile={props.isMyProfile}/> | |||
| <CategoryDetail offer={props.offer} isMyProfile={props.isMyProfile}/> | |||
| </OfferDetails> | |||
| {props.isMyProfile ? ( | |||
| <UserIconContainer onClick={handleGoProfile}> | |||
| @@ -103,23 +66,8 @@ const ItemDetailsHeaderCard = (props) => { | |||
| </MessageIcon> | |||
| )} | |||
| </HeaderTop> | |||
| <HeaderDetails> | |||
| <BottomDetails> | |||
| <StatusText> | |||
| <b>{offer?.companyData?.statistics?.publishes?.count}</b> objava | |||
| </StatusText> | |||
| <StatusText> | |||
| <b>{offer?.companyData?.statistics?.views?.count}</b> ukupnih | |||
| pregleda | |||
| </StatusText> | |||
| <StatusText> | |||
| <b>{percentOfSucceededExchanges}</b> % uspesnih trampi | |||
| </StatusText> | |||
| <StatusText> | |||
| <b>{percentOfSucceededExchanges}</b> % korektna komunikacija | |||
| </StatusText> | |||
| </BottomDetails> | |||
| </HeaderDetails> | |||
| <StatisticDetails offer={offer} /> | |||
| </ItemDetailsHeaderContainer> | |||
| ); | |||
| }; | |||
| @@ -1,9 +1,8 @@ | |||
| import { Box, Grid, Typography } from "@mui/material"; | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { Icon } from "../../Icon/Icon"; | |||
| import { ReactComponent as User} from "../../../assets/images/svg/user.svg"; | |||
| @@ -22,42 +21,12 @@ export const ItemDetailsHeaderContainer = styled(Box)` | |||
| max-width: 2000px; | |||
| position: relative; | |||
| `; | |||
| export const DetailContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| gap: 7px; | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| margin-bottom: 7px; | |||
| font-size: 12px; | |||
| @media (max-width: 600px) { | |||
| ${(props) => props.shouldHideResponsive && `display: none;`} | |||
| } | |||
| `; | |||
| export const HeaderTop = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| padding: 18px; | |||
| gap: 18px; | |||
| `; | |||
| export const HeaderDetails = styled(Box)` | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| `; | |||
| export const BottomDetails = styled(Box)` | |||
| max-width: fit-content; | |||
| display: grid; | |||
| grid-template-columns: repeat(2, 1fr); | |||
| grid-template-rows: repeat(2, 1fr); | |||
| grid-column-gap: 12px; | |||
| grid-row-gap: 12px; | |||
| padding: 18px; | |||
| @media (max-width: 600px) { | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| `; | |||
| export const OfferImage = styled.img` | |||
| border-radius: 50%; | |||
| width: 144px; | |||
| @@ -116,13 +85,6 @@ export const OfferDetails = styled(Box)` | |||
| `; | |||
| export const StatusText = styled(Grid)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| } | |||
| `; | |||
| export const OfferCategory = styled(Box)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| @@ -173,24 +135,7 @@ export const Line = styled(Box)` | |||
| width: 0; | |||
| margin: auto 0; | |||
| `; | |||
| export const DetailIcon = styled(Icon)` | |||
| display: flex; | |||
| align-items: center; | |||
| & svg { | |||
| width: 22px; | |||
| position: relative; | |||
| } | |||
| `; | |||
| export const DetailText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 16px; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| } | |||
| `; | |||
| export const CheckButton = styled(PrimaryButton)` | |||
| width: 180px; | |||
| height: 48px; | |||
| @@ -224,18 +169,6 @@ export const MessageIcon = styled(IconButton)` | |||
| } | |||
| } | |||
| `; | |||
| export const PIBIcon = styled(DetailIcon)` | |||
| position: relative; | |||
| top: 1px; | |||
| & span svg { | |||
| width: 22px; | |||
| height: 22px; | |||
| @media (max-width: 600px) { | |||
| width: 14px; | |||
| height: 14px; | |||
| } | |||
| } | |||
| `; | |||
| export const UserIconContainer = styled(MessageIcon)` | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| ` | |||
| @@ -0,0 +1,30 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| DetailContainer, | |||
| DetailIcon, | |||
| DetailText, | |||
| } from "./CategoryDetail.styled"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import { ReactComponent as Category } from "../../../../../assets/images/svg/category.svg"; | |||
| const CategoryDetail = (props) => { | |||
| const offer = props.offer; | |||
| return ( | |||
| <DetailContainer shouldHideResponsive> | |||
| <DetailIcon color={selectedTheme.iconStrokeColor} size="22px"> | |||
| <Category width={"22px"} /> | |||
| </DetailIcon> | |||
| <DetailText isMyProfile={props.isMyProfile}> | |||
| {offer?.companyData?.company?.contacts?.location} | |||
| </DetailText> | |||
| </DetailContainer> | |||
| ); | |||
| }; | |||
| CategoryDetail.propTypes = { | |||
| offer: PropTypes.any, | |||
| isMyProfile: PropTypes.bool, | |||
| }; | |||
| export default CategoryDetail; | |||
| @@ -0,0 +1,37 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import { Icon } from "../../../../Icon/Icon"; | |||
| export const DetailContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| gap: 7px; | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| margin-bottom: 7px; | |||
| font-size: 12px; | |||
| @media (max-width: 600px) { | |||
| ${(props) => props.shouldHideResponsive && `display: none;`} | |||
| } | |||
| `; | |||
| export const DetailIcon = styled(Icon)` | |||
| display: flex; | |||
| align-items: center; | |||
| & svg { | |||
| width: 22px; | |||
| position: relative; | |||
| } | |||
| `; | |||
| export const DetailText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 16px; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,30 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { DetailContainer, DetailText, PIBIcon } from "./PIBDetail.styled"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { ReactComponent as PIB } from "../../../../../assets/images/svg/pib.svg"; | |||
| const PIBDetail = (props) => { | |||
| const { t } = useTranslation(); | |||
| const offer = props.offer; | |||
| return ( | |||
| <DetailContainer> | |||
| <PIBIcon color={selectedTheme.iconStrokeColor} component="span"> | |||
| <PIB /> | |||
| </PIBIcon> | |||
| <DetailText isMyProfile={props.isMyProfile}> | |||
| {`${t("itemDetailsCard.PIB")}${offer?.companyData?.company?.PIB}`} | |||
| </DetailText> | |||
| </DetailContainer> | |||
| ); | |||
| }; | |||
| PIBDetail.propTypes = { | |||
| isMyProfile: PropTypes.bool, | |||
| offer: PropTypes.any, | |||
| icon: PropTypes.node, | |||
| }; | |||
| export default PIBDetail; | |||
| @@ -0,0 +1,49 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import { Icon } from "../../../../Icon/Icon"; | |||
| export const DetailContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| gap: 7px; | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| margin-bottom: 7px; | |||
| font-size: 12px; | |||
| @media (max-width: 600px) { | |||
| ${(props) => props.shouldHideResponsive && `display: none;`} | |||
| } | |||
| `; | |||
| export const DetailIcon = styled(Icon)` | |||
| display: flex; | |||
| align-items: center; | |||
| & svg { | |||
| width: 22px; | |||
| position: relative; | |||
| } | |||
| `; | |||
| export const DetailText = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 16px; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| } | |||
| `; | |||
| export const PIBIcon = styled(DetailIcon)` | |||
| position: relative; | |||
| top: 1px; | |||
| & span svg { | |||
| width: 22px; | |||
| height: 22px; | |||
| @media (max-width: 600px) { | |||
| width: 14px; | |||
| height: 14px; | |||
| } | |||
| } | |||
| `; | |||
| @@ -0,0 +1,60 @@ | |||
| import React, { useMemo } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| BottomDetails, | |||
| HeaderDetails, | |||
| StatusText, | |||
| StatusValue, | |||
| } from "./StatisticDetails.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const StatisticDetails = (props) => { | |||
| const { t } = useTranslation(); | |||
| const offer = props.offer; | |||
| const percentOfSucceededExchanges = useMemo(() => { | |||
| if (offer?.companyData?.statistics?.exchanges?.succeeded === 0) { | |||
| return 0 + "%"; | |||
| } else { | |||
| return ( | |||
| Math.ceil( | |||
| (offer?.companyData?.statistics?.exchanges?.total / | |||
| offer?.companyData?.statistics?.exchanges?.succeeded) * | |||
| 100 | |||
| ) + "%" | |||
| ); | |||
| } | |||
| }); | |||
| return ( | |||
| <HeaderDetails> | |||
| <BottomDetails> | |||
| <StatusText> | |||
| <StatusValue> | |||
| {offer?.companyData?.statistics?.publishes?.count} | |||
| </StatusValue> | |||
| {t("itemDetailsCard.offers")} | |||
| </StatusText> | |||
| <StatusText> | |||
| <StatusValue> | |||
| {offer?.companyData?.statistics?.views?.count} | |||
| </StatusValue> | |||
| {t("itemDetailsCard.totalViews")} | |||
| </StatusText> | |||
| <StatusText> | |||
| <StatusValue>{percentOfSucceededExchanges}</StatusValue> | |||
| {t("itemDetailsCard.successfulExchanges")} | |||
| </StatusText> | |||
| <StatusText> | |||
| <StatusValue>{percentOfSucceededExchanges}</StatusValue> | |||
| {t("itemDetailsCard.correctCommunications")} | |||
| </StatusText> | |||
| </BottomDetails> | |||
| </HeaderDetails> | |||
| ); | |||
| }; | |||
| StatisticDetails.propTypes = { | |||
| offer: PropTypes.any, | |||
| }; | |||
| export default StatisticDetails; | |||
| @@ -0,0 +1,30 @@ | |||
| import { Box, Grid } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../themes"; | |||
| export const HeaderDetails = styled(Box)` | |||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | |||
| `; | |||
| export const BottomDetails = styled(Box)` | |||
| max-width: fit-content; | |||
| display: grid; | |||
| grid-template-columns: repeat(2, 1fr); | |||
| grid-template-rows: repeat(2, 1fr); | |||
| grid-column-gap: 12px; | |||
| grid-row-gap: 12px; | |||
| padding: 18px; | |||
| @media (max-width: 600px) { | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| `; | |||
| export const StatusText = styled(Grid)` | |||
| font-family: "Open Sans"; | |||
| color: ${selectedTheme.primaryText}; | |||
| @media (max-width: 600px) { | |||
| font-size: 12px; | |||
| } | |||
| `; | |||
| export const StatusValue = styled.b` | |||
| font-weight: bold; | |||
| ` | |||
| @@ -1,59 +0,0 @@ | |||
| import React from 'react' | |||
| import {ReactComponent as DummyImage1 } from "../../assets/images/svg/dummyImages/offer-1.svg" | |||
| import {ReactComponent as DummyAuthorImage1} from "../../assets/images/svg/dummyImages/DummyAuthorImage1.svg" | |||
| // import {ReactComponent as DummyImage2 } from "../../assets/images/svg/dummyImages/offer-2.svg" | |||
| // import {ReactComponent as DummyImage3 } from "../../assets/images/svg/dummyImages/offer-3.svg" | |||
| // import {ReactComponent as DummyImage4 } from "../../assets/images/svg/dummyImages/offer-4.svg" | |||
| export const packageEnum = { | |||
| package: "PACKAGE", | |||
| palette: "PALETTE", | |||
| piece: "PIECE" | |||
| } | |||
| export const Author = { | |||
| id: 0, | |||
| image: <DummyAuthorImage1 />, | |||
| title: "Women's Beauty House", | |||
| pib: 123456789, | |||
| location: "Nis, Serbia", | |||
| numberOfOffers: 9, | |||
| numberOfViews: 1200, | |||
| successSwapsProcent: "75%", | |||
| goodCommunicationProcent: "90%", | |||
| } | |||
| export const Offer = { | |||
| id: 0, | |||
| title: "Vino", | |||
| category: "Hrana i pice", | |||
| subcategory:"Farbe", | |||
| status:"novo", | |||
| quantity:150, | |||
| numberOfViews:45, | |||
| description: "Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.Vinarija Aleksić osnovana je u Vranju 2006. godine, otvorivši time put oživljavanju vinogradarstva na jugu Srbije.", | |||
| images: [ | |||
| { | |||
| id:0, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:1, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:2, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:3, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| { | |||
| id:4, | |||
| image: <DummyImage1 /> | |||
| }, | |||
| ], | |||
| package: packageEnum.package, | |||
| postDate: "12.04.2022", | |||
| } | |||
| @@ -1,26 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| const BlockSectionLoader = ({ children, isLoading, fullHeight, noShadow }) => ( | |||
| <div | |||
| className={`c-loader__wrapper c-loader__wrapper--block ${ | |||
| fullHeight ? 'c-loader__wrapper--full-height' : '' | |||
| } ${noShadow ? 'c-loader__wrapper--no-shadow' : ''}`} | |||
| > | |||
| {children} | |||
| {isLoading && ( | |||
| <div className="c-loader"> | |||
| <div className="c-loader__icon" /> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| BlockSectionLoader.propTypes = { | |||
| children: PropTypes.node, | |||
| isLoading: PropTypes.bool, | |||
| fullHeight: PropTypes.bool, | |||
| noShadow: PropTypes.bool, | |||
| }; | |||
| export default BlockSectionLoader; | |||
| @@ -1,13 +0,0 @@ | |||
| import React from "react"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/big-logo-vertical.svg"; | |||
| import { FullPageLoaderContainer } from "./FullPageLoader.styled"; | |||
| const FullPageLoader = () => { | |||
| return ( | |||
| <FullPageLoaderContainer> | |||
| <Logo /> | |||
| </FullPageLoaderContainer> | |||
| ); | |||
| }; | |||
| export default FullPageLoader; | |||
| @@ -1,10 +0,0 @@ | |||
| import { Container } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const FullPageLoaderContainer = styled(Container)` | |||
| height: 100%; | |||
| width: 100vw; | |||
| padding-top: 250px; | |||
| text-align: center; | |||
| ` | |||
| @@ -1,20 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| const SectionLoader = ({ children, isLoading }) => ( | |||
| <div className="c-loader__wrapper"> | |||
| {children} | |||
| {isLoading && ( | |||
| <div className="c-loader"> | |||
| <div className="c-loader__icon" /> | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| SectionLoader.propTypes = { | |||
| children: PropTypes.node, | |||
| isLoading: PropTypes.bool, | |||
| }; | |||
| export default SectionLoader; | |||
| @@ -0,0 +1,19 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { LoginDescription as Description } from "./LoginDescription.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const LoginDescription = () => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Description component="h1" variant="h6"> | |||
| {t("login.welcomeText")} | |||
| </Description> | |||
| ); | |||
| }; | |||
| LoginDescription.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default LoginDescription; | |||
| @@ -0,0 +1,18 @@ | |||
| import { Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const LoginDescription = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| margin-top: 9px; | |||
| width: 221px; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| display: flex; | |||
| align-items: center; | |||
| text-align: center; | |||
| color: ${selectedTheme.primaryGrayText}; | |||
| margin-bottom: 20px; | |||
| `; | |||
| @@ -0,0 +1,26 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectLoginError } from "../../../store/selectors/loginSelectors"; | |||
| import { ErrorText } from "./ErrorMessage.styled"; | |||
| const ErrorMessage = (props) => { | |||
| const formik = props.formik; | |||
| const error = useSelector(selectLoginError); | |||
| return ( | |||
| <> | |||
| {formik.errors.password && formik.touched.password && ( | |||
| <ErrorText>{formik.errors.password}</ErrorText> | |||
| )} | |||
| {error.length > 0 && !formik.errors.password && ( | |||
| <ErrorText>{error}</ErrorText> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| ErrorMessage.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default ErrorMessage; | |||
| @@ -0,0 +1,11 @@ | |||
| import { Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const ErrorText = styled(Typography)` | |||
| color: red; | |||
| font-family: "Open Sans"; | |||
| position: relative; | |||
| top: -12px; | |||
| height: 20px; | |||
| font-size: 14px; | |||
| `; | |||
| @@ -0,0 +1,30 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { selectLoginError } from "../../../../store/selectors/loginSelectors"; | |||
| import { useSelector } from "react-redux"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const EmailField = (props) => { | |||
| const { t } = useTranslation(); | |||
| const error = useSelector(selectLoginError); | |||
| const formik = props.formik; | |||
| return ( | |||
| <TextField | |||
| name="email" | |||
| placeholder={t("common.labelEmail")} | |||
| value={formik.values.email} | |||
| onChange={formik.handleChange} | |||
| error={(formik.touched.email && formik.errors.email) || error.length > 0} | |||
| helperText={formik.touched.email && formik.errors.email} | |||
| autoFocus | |||
| fullWidth | |||
| /> | |||
| ); | |||
| }; | |||
| EmailField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default EmailField; | |||
| @@ -0,0 +1,50 @@ | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import IconButton from "../../../IconButton/IconButton"; | |||
| import { ReactComponent as VisibilityOn } from "../../../../assets/images/svg/eye-striked.svg"; | |||
| import { ReactComponent as VisibilityOff } from "../../../../assets/images/svg/eye.svg"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectLoginError } from "../../../../store/selectors/loginSelectors"; | |||
| import { TextField } from "../../../TextFields/TextField/TextField"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const PasswordField = (props) => { | |||
| const formik = props.formik; | |||
| const [showPassword, setShowPassword] = useState(false); | |||
| const handleClickShowPassword = () => setShowPassword(!showPassword); | |||
| const handleMouseDownPassword = () => setShowPassword(!showPassword); | |||
| const error = useSelector(selectLoginError); | |||
| const {t} = useTranslation(); | |||
| return ( | |||
| <TextField | |||
| name="password" | |||
| placeholder={t("common.labelPassword")} | |||
| margin="normal" | |||
| type={showPassword ? "text" : "password"} | |||
| value={formik.values.password} | |||
| onChange={formik.handleChange} | |||
| error={ | |||
| (formik.touched.password && formik.errors.password) || error.length > 0 | |||
| } | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <IconButton | |||
| onClick={handleClickShowPassword} | |||
| onMouseDown={handleMouseDownPassword} | |||
| > | |||
| {showPassword ? <VisibilityOn /> : <VisibilityOff />} | |||
| </IconButton> | |||
| ), | |||
| }} | |||
| /> | |||
| ); | |||
| }; | |||
| PasswordField.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default PasswordField; | |||
| @@ -0,0 +1,34 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectLoginError } from "../../../store/selectors/loginSelectors"; | |||
| import { FORGOT_PASSWORD_PAGE } from "../../../constants/pages"; | |||
| import Link from "../../Link/Link"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { NavLink } from "react-router-dom"; | |||
| const ForgotPasswordLink = () => { | |||
| const error = useSelector(selectLoginError); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Link | |||
| to={FORGOT_PASSWORD_PAGE} | |||
| textsize="12px" | |||
| component={NavLink} | |||
| underline="hover" | |||
| align="right" | |||
| style={{ | |||
| marginTop: error.length > 0 ? "0" : "18px", | |||
| marginBottom: "18px", | |||
| }} | |||
| > | |||
| {t("login.forgotYourPassword")} | |||
| </Link> | |||
| ); | |||
| }; | |||
| ForgotPasswordLink.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default ForgotPasswordLink; | |||
| @@ -0,0 +1,98 @@ | |||
| import React, { useEffect } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useFormik } from "formik"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { | |||
| clearLoginErrors, | |||
| fetchLogin, | |||
| } from "../../store/actions/login/loginActions"; | |||
| import { selectLoginError } from "../../store/selectors/loginSelectors"; | |||
| import { HOME_PAGE } from "../../constants/pages"; | |||
| import { ReactComponent as Logo } from "../../assets/images/svg/logo-vertical.svg"; | |||
| import { LoginPageContainer, LoginFormContainer } from "./Login.styled"; | |||
| import loginValidation from "../../validations/loginValidation"; | |||
| import loginInitialValues from "../../initialValues/loginInitialValues"; | |||
| import LoginTitle from "./Title/LoginTitle"; | |||
| import LoginDescription from "./Description/LoginDescription"; | |||
| import EmailField from "./Fields/Email/EmailField"; | |||
| import PasswordField from "./Fields/Password/PasswordField"; | |||
| import ErrorMessage from "./ErrorMessage/ErrorMessage"; | |||
| import ForgotPasswordLink from "./ForgotPasswordLink/ForgotPasswordLink"; | |||
| import LoginButton from "./LoginButton/LoginButton"; | |||
| import RegisterLink from "./RegisterLink/RegisterLink"; | |||
| const Login = () => { | |||
| const dispatch = useDispatch(); | |||
| const error = useSelector(selectLoginError); | |||
| const history = useHistory(); | |||
| useEffect(() => { | |||
| dispatch(clearLoginErrors()); | |||
| }, []); | |||
| const handleApiResponseSuccess = () => { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| const { email, password } = values; | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchLogin({ | |||
| email, | |||
| password, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| console.log(values); | |||
| }; | |||
| const formik = useFormik({ | |||
| initialValues: loginInitialValues, | |||
| validationSchema: loginValidation, | |||
| onSubmit: handleSubmit, | |||
| validateOnBlur: true, | |||
| enableReinitialize: true, | |||
| }); | |||
| useEffect(() => { | |||
| if (error) { | |||
| if (formik.errors.email || formik.errors.password) { | |||
| dispatch(clearLoginErrors()); | |||
| } | |||
| } | |||
| }, [formik.errors.email, formik.errors.password]); | |||
| return ( | |||
| <LoginPageContainer> | |||
| <Logo /> | |||
| <LoginTitle /> | |||
| <LoginDescription /> | |||
| <LoginFormContainer component="form" onSubmit={formik.handleSubmit}> | |||
| <EmailField formik={formik} /> | |||
| <PasswordField formik={formik} /> | |||
| <ErrorMessage formik={formik} /> | |||
| <ForgotPasswordLink /> | |||
| <LoginButton formik={formik} /> | |||
| <RegisterLink /> | |||
| </LoginFormContainer> | |||
| </LoginPageContainer> | |||
| ); | |||
| }; | |||
| Login.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default Login; | |||
| @@ -0,0 +1,19 @@ | |||
| import { Box, Container } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const LoginPageContainer = styled(Container)` | |||
| margin-top: 150px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @media (max-height: 900px) { | |||
| margin-top: 110px; | |||
| } | |||
| @media (max-height: 800px) { | |||
| margin-top: 70px; | |||
| } | |||
| `; | |||
| export const LoginFormContainer = styled(Box)` | |||
| width: 335px; | |||
| height: 216px; | |||
| `; | |||
| @@ -0,0 +1,32 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | |||
| import { useTranslation } from "react-i18next"; | |||
| const LoginButton = (props) => { | |||
| const { t } = useTranslation(); | |||
| const formik = props.formik; | |||
| return ( | |||
| <PrimaryButton | |||
| type="submit" | |||
| variant="contained" | |||
| height="48px" | |||
| fullWidth | |||
| buttoncolor={selectedTheme.primaryPurple} | |||
| onClick={formik.handleSubmit} | |||
| textcolor="white" | |||
| disabled={ | |||
| formik.values.email.length === 0 || formik.values.password.length === 0 | |||
| } | |||
| > | |||
| {t("login.logIn")} | |||
| </PrimaryButton> | |||
| ); | |||
| }; | |||
| LoginButton.propTypes = { | |||
| formik: PropTypes.any, | |||
| }; | |||
| export default LoginButton; | |||
| @@ -0,0 +1,27 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { RegisterAltText, RegisterTextContainer } from "./RegisterLink.styled"; | |||
| import Link from "../../Link/Link"; | |||
| import { NavLink } from "react-router-dom"; | |||
| const RegisterLink = () => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <RegisterTextContainer> | |||
| <RegisterAltText> | |||
| {t("login.dontHaveAccount").padEnd(2, " ")} | |||
| </RegisterAltText> | |||
| <Link to="/register" component={NavLink} underline="hover" align="center"> | |||
| {t("login.signUp")} | |||
| </Link> | |||
| </RegisterTextContainer> | |||
| ); | |||
| }; | |||
| RegisterLink.propTypes = { | |||
| children: PropTypes.any, | |||
| }; | |||
| export default RegisterLink; | |||
| @@ -0,0 +1,20 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const RegisterAltText = styled(Typography)` | |||
| font-family: "Poppins"; | |||
| color: ${selectedTheme.primaryText}; | |||
| font-size: 14px; | |||
| padding-right: 6px; | |||
| line-height: 14px; | |||
| `; | |||
| export const RegisterTextContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| margin-top: 36px; | |||
| justify-content: center; | |||
| @media (max-width: 600px) { | |||
| padding-bottom: 36px; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,19 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { LoginTitle as Title } from "./LoginTitle.styled"; | |||
| const LoginTitle = () => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <Title component="h1" variant="h5"> | |||
| {t("login.logInTitle")} | |||
| </Title> | |||
| ); | |||
| }; | |||
| LoginTitle.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default LoginTitle; | |||
| @@ -0,0 +1,17 @@ | |||
| import { Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const LoginTitle = styled(Typography)` | |||
| font-family: "Open Sans"; | |||
| width: 328px; | |||
| height: 33px; | |||
| text-align: center; | |||
| flex: 1; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 24px; | |||
| line-height: 33px; | |||
| color: ${selectedTheme.primaryPurple}; | |||
| margin-top: 36px; | |||
| `; | |||
| @@ -1,57 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogTitle, | |||
| DialogActions, | |||
| Button, | |||
| useMediaQuery, | |||
| useTheme, | |||
| } from '@mui/material'; | |||
| const DialogComponent = ({ | |||
| title, | |||
| content, | |||
| onClose, | |||
| open, | |||
| maxWidth, | |||
| fullWidth, | |||
| responsive, | |||
| }) => { | |||
| const theme = useTheme(); | |||
| const fullScreen = useMediaQuery(theme.breakpoints.down('md')); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| return ( | |||
| <Dialog | |||
| maxWidth={maxWidth} | |||
| fullWidth={fullWidth} | |||
| fullScreen={responsive && fullScreen} | |||
| onClose={handleClose} | |||
| open={open} | |||
| > | |||
| <DialogTitle>{title}</DialogTitle> | |||
| {content && <DialogContent>{content}</DialogContent>} | |||
| <DialogActions> | |||
| <Button onClick={handleClose}>OK</Button> | |||
| <Button onClick={handleClose}>Cancel</Button> | |||
| </DialogActions> | |||
| </Dialog> | |||
| ); | |||
| }; | |||
| DialogComponent.propTypes = { | |||
| title: PropTypes.string, | |||
| open: PropTypes.bool.isRequired, | |||
| content: PropTypes.any, | |||
| onClose: PropTypes.func.isRequired, | |||
| maxWidth: PropTypes.any, | |||
| fullWidth: PropTypes.bool, | |||
| responsive: PropTypes.bool, | |||
| }; | |||
| export default DialogComponent; | |||
| @@ -1,28 +1,28 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { Drawer } from '@mui/material'; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { Drawer } from "@mui/material"; | |||
| const DrawerComponent = ({ open, toggleOpen, content, anchor = 'right' }) => ( | |||
| <Drawer | |||
| sx={{ | |||
| minWidth: 250, | |||
| '& .MuiDrawer-paper': { | |||
| minWidth: 250, | |||
| }, | |||
| }} | |||
| anchor={anchor} | |||
| open={open} | |||
| onClose={toggleOpen} | |||
| > | |||
| {content ? content : null} | |||
| </Drawer> | |||
| const DrawerComponent = ({ open, toggleOpen, content, anchor = "right" }) => ( | |||
| <Drawer | |||
| sx={{ | |||
| minWidth: 250, | |||
| "& .MuiDrawer-paper": { | |||
| minWidth: 250, | |||
| }, | |||
| }} | |||
| anchor={anchor} | |||
| open={open} | |||
| onClose={toggleOpen} | |||
| > | |||
| {content ? content : null} | |||
| </Drawer> | |||
| ); | |||
| DrawerComponent.propTypes = { | |||
| open: PropTypes.bool, | |||
| toggleOpen: PropTypes.func, | |||
| content: PropTypes.any, | |||
| anchor: PropTypes.oneOf(['top', 'right', 'left', 'bottom']), | |||
| open: PropTypes.bool, | |||
| toggleOpen: PropTypes.func, | |||
| content: PropTypes.any, | |||
| anchor: PropTypes.oneOf(["top", "right", "left", "bottom"]), | |||
| }; | |||
| export default DrawerComponent; | |||