| "react/jsx-filename-extension": "off", | "react/jsx-filename-extension": "off", | ||||
| "react/jsx-props-no-spreading": "off", | "react/jsx-props-no-spreading": "off", | ||||
| "react/button-has-type": "off", | "react/button-has-type": "off", | ||||
| "react/max-len": ["error", { | |||||
| "code": 100 | |||||
| }], | |||||
| "react/require-default-props": "off", | "react/require-default-props": "off", | ||||
| "import/no-extraneous-dependencies": "off", | "import/no-extraneous-dependencies": "off", | ||||
| "import/prefer-default-export": "off", | "import/prefer-default-export": "off", |
| { | { | ||||
| "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] | |||||
| } | |||||
| } | } |
| <Router history={history}> | <Router history={history}> | ||||
| <Helmet> | <Helmet> | ||||
| <title>{i18next.t("app.title")}</title> | <title>{i18next.t("app.title")}</title> | ||||
| </Helmet> | </Helmet> | ||||
| <StyledEngineProvider injectFirst> | <StyledEngineProvider injectFirst> | ||||
| {/* <button onClick={handleClick}>Kik</button> */} | {/* <button onClick={handleClick}>Kik</button> */} |
| const AppRoutes = () => { | const AppRoutes = () => { | ||||
| return ( | return ( | ||||
| <Switch> | <Switch> | ||||
| <Route exact path={BASE_PAGE} component={LoginPage} /> | |||||
| <Route exact path={BASE_PAGE} component={HomePage} /> | |||||
| <Route exact path={LOGIN_PAGE} component={LoginPage} /> | <Route exact path={LOGIN_PAGE} component={LoginPage} /> | ||||
| <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | <Route path={NOT_FOUND_PAGE} component={NotFoundPage} /> | ||||
| <Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | <Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> |
| import React, { useMemo } from "react"; | import React, { useMemo } from "react"; | ||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { | import { | ||||
| CheckButton, | |||||
| OfferImage, | |||||
| OfferTitle, | |||||
| ChatOffer, | |||||
| Commands, | |||||
| ChatInfo, | ChatInfo, | ||||
| OfferText, | |||||
| ChatCardContainer, | ChatCardContainer, | ||||
| Col, | Col, | ||||
| UserImage, | UserImage, | ||||
| OfferCardContainer, | |||||
| UserImgWrapper, | UserImgWrapper, | ||||
| OfferImgWrapper, | |||||
| UserName, | UserName, | ||||
| LastMessage, | LastMessage, | ||||
| Line, | Line, | ||||
| LocationContainer, | |||||
| XSText, | |||||
| LocationIcon, | |||||
| OfferCardContainerMobile, | |||||
| OfferTextMobile, | |||||
| OfferTitleMobile, | |||||
| PhoneIconContainer, | |||||
| PhoneIcon, | |||||
| LocationIconContainer, | |||||
| } from "./ChatCard.styled"; | } from "./ChatCard.styled"; | ||||
| import selectedTheme from "../../../themes"; | |||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| import useScreenDimensions from "../../../hooks/useScreenDimensions"; | 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 ChatCard = (props) => { | ||||
| const history = useHistory(); | const history = useHistory(); | ||||
| const dimensions = useScreenDimensions(); | const dimensions = useScreenDimensions(); | ||||
| const { t } = useTranslation(); | |||||
| const chat = useMemo(() => { | const chat = useMemo(() => { | ||||
| return props.chat; | return props.chat; | ||||
| } | } | ||||
| return ""; | return ""; | ||||
| }, [chat]); | }, [chat]); | ||||
| const routeToItem = () => { | const routeToItem = () => { | ||||
| history.push(`/messages/${chat?.chat?._id}`); | history.push(`/messages/${chat?.chat?._id}`); | ||||
| }; | }; | ||||
| <UserName>{chat?.interlocutorData?.name}</UserName> | <UserName>{chat?.interlocutorData?.name}</UserName> | ||||
| {/* Only shows on Mobile */} | {/* Only shows on Mobile */} | ||||
| <OfferCardContainerMobile> | |||||
| <OfferTextMobile>{t("messages.cardProduct")}</OfferTextMobile> | |||||
| <OfferTitleMobile>{chat?.offerData?.name}</OfferTitleMobile> | |||||
| </OfferCardContainerMobile> | |||||
| <MobileOfferDetails chat={chat} /> | |||||
| {/* ^^^^^ */} | {/* ^^^^^ */} | ||||
| <LastMessage>{lastMessage}</LastMessage> | <LastMessage>{lastMessage}</LastMessage> | ||||
| <LocationContainer> | |||||
| <LocationIconContainer> | |||||
| <LocationIcon /> | |||||
| </LocationIconContainer> | |||||
| <XSText>{chat?.interlocutorData?.location}</XSText> | |||||
| </LocationContainer> | |||||
| <OfferLocation chat={chat} /> | |||||
| </ChatInfo> | </ChatInfo> | ||||
| </Col> | </Col> | ||||
| <Line /> | <Line /> | ||||
| {/* Only shows on Desktop */} | {/* 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> | </ChatCardContainer> | ||||
| ); | ); | ||||
| }; | }; |
| import { Box, Container, Typography } from "@mui/material"; | import { Box, Container, Typography } from "@mui/material"; | ||||
| import styled from "styled-components"; | import styled from "styled-components"; | ||||
| import selectedTheme from "../../../themes"; | 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)` | export const ChatCardContainer = styled(Container)` | ||||
| display: flex; | display: flex; | ||||
| box-sizing: border-box; | box-sizing: border-box; | ||||
| margin: 10px 0; | margin: 10px 0; | ||||
| background-color: ${(props) => | background-color: ${(props) => | ||||
| props.sponsored === "true" | |||||
| ? selectedTheme.backgroundSponsoredColor | |||||
| : "white"}; | |||||
| props.sponsored === "true" ? selectedTheme.backgroundSponsoredColor : "white"}; | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| ${(props) => | ${(props) => | ||||
| props.sponsored === "true" && | |||||
| `border: 1px solid ${selectedTheme.borderSponsoredColor};`} | |||||
| props.sponsored === "true" && `border: 1px solid ${selectedTheme.borderSponsoredColor};`} | |||||
| padding: 16px; | padding: 16px; | ||||
| max-width: 2000px; | max-width: 2000px; | ||||
| height: 180px; | height: 180px; | ||||
| margin: 0; | margin: 0; | ||||
| ${(props) => | ${(props) => | ||||
| props.vertical && | props.vertical && | ||||
| ` | |||||
| height: 330px; | |||||
| `height: 330px; | |||||
| width: 180px; | width: 180px; | ||||
| margin: 0 18px; | |||||
| `} | |||||
| margin: 0 18px;`} | |||||
| } | } | ||||
| `; | `; | ||||
| export const UserImage = styled.img` | export const UserImage = styled.img` | ||||
| height: 72px; | height: 72px; | ||||
| } | } | ||||
| `; | `; | ||||
| export const UserImgWrapper = styled(Box)` | export const UserImgWrapper = styled(Box)` | ||||
| overflow: hidden; | overflow: hidden; | ||||
| border-radius: 50%; | border-radius: 50%; | ||||
| min-width: 80px; | 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)` | export const ChatInfo = styled(Box)` | ||||
| height: 100%; | height: 100%; | ||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| `; | `; | ||||
| export const Col = styled(Box)` | export const Col = styled(Box)` | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| flex-direction: row; | flex-direction: row; | ||||
| gap: 18px; | gap: 18px; | ||||
| flex: 1; | flex: 1; | ||||
| @media (max-width: 1024px) { | |||||
| ${(props) => props.mobileDisappear && `display: none;`} | |||||
| } | |||||
| @media (max-width: 600px) { | @media (max-width: 600px) { | ||||
| ${(props) => props.mobileDisappear && `display: none;`} | |||||
| ${props => props.mobileDisappear && 'display: none;'} | |||||
| } | } | ||||
| `; | `; | ||||
| export const UserName = styled(Typography)` | export const UserName = styled(Typography)` | ||||
| margin-bottom: 12px; | margin-bottom: 12px; | ||||
| font-family: "Open Sans"; | font-family: "Open Sans"; | ||||
| font-size: 18px; | font-size: 18px; | ||||
| } | } | ||||
| `; | `; | ||||
| export const LastMessage = styled(Typography)` | export const LastMessage = styled(Typography)` | ||||
| font-family: "Open Sans"; | font-family: "Open Sans"; | ||||
| color: ${selectedTheme.primaryDarkTextThird}; | color: ${selectedTheme.primaryDarkTextThird}; | ||||
| display: none; | 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)` | export const Line = styled(Box)` | ||||
| border-left: 1px solid rgba(0, 0, 0, 0.15); | border-left: 1px solid rgba(0, 0, 0, 0.15); | ||||
| height: 100px; | height: 100px; | ||||
| margin: auto 0; | margin: auto 0; | ||||
| @media (max-width: 600px) { | @media (max-width: 600px) { | ||||
| display: none; | display: none; | ||||
| } | |||||
| `; | |||||
| export const PhoneIcon = styled(Phone)` | |||||
| @media (max-width: 600px) { | |||||
| width: 14px; | |||||
| height: 14px; | |||||
| position: relative; | |||||
| top: 1px; | |||||
| } | |||||
| `; | |||||
| }`; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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;`} | |||||
| } | |||||
| `; |
| 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; |
| 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}; | |||||
| `; |
| 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 |
| 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; | |||||
| `; |
| category, | category, | ||||
| condition, | condition, | ||||
| description, | description, | ||||
| // images, | |||||
| images, | |||||
| location, | location, | ||||
| nameOfProduct, | nameOfProduct, | ||||
| subcategory, | subcategory, | ||||
| } = informations; | } = informations; | ||||
| if (stepNumber === 1) { | if (stepNumber === 1) { | ||||
| setInformations({}); | |||||
| setInformations({ | |||||
| category, | |||||
| condition, | |||||
| description, | |||||
| location, | |||||
| nameOfProduct, | |||||
| subcategory, | |||||
| }); | |||||
| } | } | ||||
| if (stepNumber === 2) { | if (stepNumber === 2) { | ||||
| setInformations({ | setInformations({ | ||||
| category, | category, | ||||
| condition, | condition, | ||||
| description, | description, | ||||
| images, | |||||
| location, | location, | ||||
| nameOfProduct, | nameOfProduct, | ||||
| subcategory, | subcategory, | ||||
| functions={[() => goStepBack(1), () => goStepBack(2)]} | functions={[() => goStepBack(1), () => goStepBack(2)]} | ||||
| /> | /> | ||||
| {currentStep === 1 && ( | {currentStep === 1 && ( | ||||
| <FirstPartCreateOffer handleNext={handleNext} offer={offer} /> | |||||
| <FirstPartCreateOffer | |||||
| handleNext={handleNext} | |||||
| offer={offer} | |||||
| informations={informations} | |||||
| /> | |||||
| )} | )} | ||||
| {currentStep === 2 && ( | {currentStep === 2 && ( | ||||
| <SecondPartCreateOffer handleNext={handleNext} offer={offer} /> | |||||
| <SecondPartCreateOffer | |||||
| handleNext={handleNext} | |||||
| offer={offer} | |||||
| informations={informations} | |||||
| /> | |||||
| )} | )} | ||||
| {currentStep === 3 && ( | {currentStep === 3 && ( | ||||
| <ThirdPartCreateOffer | <ThirdPartCreateOffer |
| const { t } = useTranslation(); | 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(() => { | useEffect(() => { | ||||
| if (props.offer !== undefined) { | if (props.offer !== undefined) { | ||||
| let scat = categories.filter( | let scat = categories.filter( | ||||
| (cat) => cat.name === props.offer.category.name | (cat) => cat.name === props.offer.category.name | ||||
| ); | ); | ||||
| console.log(scat[0].subcategories.map((x) => x.name)); | |||||
| setSubcat(scat[0].subcategories.map((x) => x.name)); | setSubcat(scat[0].subcategories.map((x) => x.name)); | ||||
| } | } | ||||
| }, [props.offer]); | }, [props.offer]); | ||||
| }; | }; | ||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | 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({ | validationSchema: Yup.object().shape({ | ||||
| nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")), | nameOfProduct: Yup.string().required(t("login.nameOfProductRequired")), | ||||
| <FieldLabel leftText={t("offer.location")} /> | <FieldLabel leftText={t("offer.location")} /> | ||||
| <SelectField | <SelectField | ||||
| defaultValue={ | |||||
| props.offer === undefined ? "default" : props.offer.location.city | |||||
| } | |||||
| defaultValue={formik.values.location} | |||||
| onChange={(value) => { | onChange={(value) => { | ||||
| formik.setFieldValue("location", value.target.value); | formik.setFieldValue("location", value.target.value); | ||||
| }} | }} | ||||
| value={formik.values.location} | |||||
| > | > | ||||
| <SelectOption value="default"> | <SelectOption value="default"> | ||||
| {t("offer.choseLocation")} | {t("offer.choseLocation")} | ||||
| </SelectOption> | </SelectOption> | ||||
| {locations.map((loc) => { | {locations.map((loc) => { | ||||
| return ( | return ( | ||||
| <SelectOption key={loc._if} value={loc.city}> | |||||
| <SelectOption key={loc._id} value={loc.city}> | |||||
| {loc.city} | {loc.city} | ||||
| </SelectOption> | </SelectOption> | ||||
| ); | ); | ||||
| <FieldLabel leftText={t("offer.category")} /> | <FieldLabel leftText={t("offer.category")} /> | ||||
| <SelectField | <SelectField | ||||
| defaultValue={ | |||||
| props.offer === undefined ? "default" : props.offer.category.name | |||||
| } | |||||
| defaultValue={formik.values.category} | |||||
| onChange={(value) => { | onChange={(value) => { | ||||
| formik.setFieldValue("category", value.target.value); | formik.setFieldValue("category", value.target.value); | ||||
| }} | }} | ||||
| value={formik.values.category} | |||||
| > | > | ||||
| <SelectOption value="default"> | <SelectOption value="default"> | ||||
| {t("offer.choseCategory")} | {t("offer.choseCategory")} | ||||
| <FieldLabel leftText={t("offer.subcategory")} /> | <FieldLabel leftText={t("offer.subcategory")} /> | ||||
| <SelectField | <SelectField | ||||
| defaultValue={ | |||||
| props.offer === undefined ? "default" : props.offer.subcategory | |||||
| } | |||||
| // defaultValue="default" | |||||
| defaultValue={formik.values.subcategory} | |||||
| onChange={(value) => { | onChange={(value) => { | ||||
| formik.setFieldValue("subcategory", value.target.value); | formik.setFieldValue("subcategory", value.target.value); | ||||
| }} | }} | ||||
| value={formik.values.subcategory} | |||||
| > | > | ||||
| <SelectOption value="default"> | <SelectOption value="default"> | ||||
| {t("offer.choseSubcategory")} | {t("offer.choseSubcategory")} | ||||
| !formik.values?.description || | !formik.values?.description || | ||||
| formik.values?.category?.length === 0 || | formik.values?.category?.length === 0 || | ||||
| !formik.values?.category || | !formik.values?.category || | ||||
| formik.values?.category === "default" || | |||||
| formik.values?.subcategory?.length === 0 || | formik.values?.subcategory?.length === 0 || | ||||
| !formik.values?.subcategory || | !formik.values?.subcategory || | ||||
| formik.values?.subcategory === "default" || | |||||
| formik.values?.location?.length === 0 || | formik.values?.location?.length === 0 || | ||||
| !formik.values?.location | |||||
| !formik.values?.location || | |||||
| formik.values?.location === "default" | |||||
| } | } | ||||
| > | > | ||||
| {t("offer.continue")} | {t("offer.continue")} | ||||
| children: PropTypes.any, | children: PropTypes.any, | ||||
| handleNext: PropTypes.func, | handleNext: PropTypes.func, | ||||
| offer: PropTypes.node, | offer: PropTypes.node, | ||||
| informations: PropTypes.any, | |||||
| }; | }; | ||||
| export default FirstPartCreateOffer; | export default FirstPartCreateOffer; |
| ); // 3 images | ); // 3 images | ||||
| const { t } = useTranslation(); | 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(() => { | useEffect(() => { | ||||
| setImages((prevState) => { | setImages((prevState) => { | ||||
| let editedImages = [...prevState]; | let editedImages = [...prevState]; | ||||
| props.handleNext(values); | props.handleNext(values); | ||||
| }; | }; | ||||
| const conditionSelectEnumArray = Object.values(conditionSelectEnum); | |||||
| const filteredconditionSelectEnumArray = conditionSelectEnumArray.map( | |||||
| (item) => item.mainText | |||||
| ); | |||||
| const formik = useFormik({ | const formik = useFormik({ | ||||
| initialValues: { | initialValues: { | ||||
| images: images, | 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, | onSubmit: handleSubmit, | ||||
| validateOnBlur: true, | validateOnBlur: true, | ||||
| enableReinitialize: true, | enableReinitialize: true, | ||||
| <InputButtonContainer> | <InputButtonContainer> | ||||
| <FieldLabel leftText={t("offer.condition")} /> | <FieldLabel leftText={t("offer.condition")} /> | ||||
| <SelectField | <SelectField | ||||
| defaultValue={ | |||||
| props.offer === undefined ? "default" : props.offer.condition | |||||
| } | |||||
| onChange={(value) => { | onChange={(value) => { | ||||
| formik.setFieldValue("condition", value.target.value); | formik.setFieldValue("condition", value.target.value); | ||||
| }} | }} | ||||
| value={formik.values.condition} | |||||
| > | > | ||||
| <SelectOption value="default"> | <SelectOption value="default"> | ||||
| {t("offer.choseCondition")} | {t("offer.choseCondition")} | ||||
| textcolor="white" | textcolor="white" | ||||
| onClick={formik.handleSubmit} | onClick={formik.handleSubmit} | ||||
| disabled={ | 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")} | {t("offer.continue")} | ||||
| children: PropTypes.node, | children: PropTypes.node, | ||||
| handleNext: PropTypes.func, | handleNext: PropTypes.func, | ||||
| offer: PropTypes.node, | offer: PropTypes.node, | ||||
| informations: PropTypes.any, | |||||
| }; | }; | ||||
| export default SecondPartCreateOffer; | export default SecondPartCreateOffer; |
| <LocationChoser filters={filters} /> | <LocationChoser filters={filters} /> | ||||
| </ContentContainer> | </ContentContainer> | ||||
| <FilterFooter /> | |||||
| <FilterFooter | |||||
| closeResponsive={props.closeResponsive} | |||||
| responsiveOpen={props.responsiveOpen} | |||||
| /> | |||||
| </FilterCardContainer> | </FilterCardContainer> | ||||
| ); | ); | ||||
| }; | }; |
| 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; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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}; | |||||
| } | |||||
| `; |
| import React, { useEffect, useState } from "react"; | import React, { useEffect, useState } from "react"; | ||||
| import PropTypes from "prop-types"; | 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 DropdownItem from "../../../../Dropdown/DropdownItem/DropdownItem"; | ||||
| import { CheckBox } from "../../../../CheckBox/CheckBox"; | |||||
| import CheckboxDropdownList from "./CheckboxDropdownList/CheckboxDropdownList"; | |||||
| import Checkbox from "./Checkbox/Checkbox"; | |||||
| const FilterCheckboxDropdown = (props) => { | const FilterCheckboxDropdown = (props) => { | ||||
| const [toSearch, setToSearch] = useState(""); | |||||
| const [dataToShow, setDataToShow] = useState([]); | const [dataToShow, setDataToShow] = useState([]); | ||||
| const [isOpened, setIsOpened] = useState(false); | const [isOpened, setIsOpened] = useState(false); | ||||
| const [toSearch, setToSearch] = useState(""); | |||||
| const { data } = props; | const { data } = props; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setDataToShow([...data]); | setDataToShow([...data]); | ||||
| }, [data]); | }, [data]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (toSearch.length > 0) { | if (toSearch.length > 0) { | ||||
| setDataToShow( | setDataToShow( | ||||
| } | } | ||||
| }, [toSearch]); | }, [toSearch]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (props.filters?.length > 0) { | if (props.filters?.length > 0) { | ||||
| setIsOpened(true) | |||||
| setIsOpened(true); | |||||
| } | } | ||||
| }, [props.filters]) | |||||
| }, [props.filters]); | |||||
| const handleChange = (item) => { | const handleChange = (item) => { | ||||
| if (props.oneValueAllowed) { | if (props.oneValueAllowed) { | ||||
| props.setItemsSelected([item]); | props.setItemsSelected([item]); | ||||
| } else { | } 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 { | } else { | ||||
| props.setItemsSelected([...props.filters, item]); | props.setItemsSelected([...props.filters, item]); | ||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| const handleDelete = (item) => { | |||||
| props.setItemsSelected([...props.filters.filter((p) => p !== item)]); | |||||
| }; | |||||
| const handleClear = () => { | |||||
| setToSearch(""); | |||||
| }; | |||||
| return ( | return ( | ||||
| <DropdownList | |||||
| <CheckboxDropdownList | |||||
| toSearch={toSearch} | |||||
| setToSearch={setToSearch} | |||||
| title={props.title} | 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} | 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) => { | {dataToShow.map((item) => { | ||||
| return ( | return ( | ||||
| <DropdownItem key={item.city}> | <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)} | onChange={() => handleChange(item)} | ||||
| fullWidth | |||||
| /> | /> | ||||
| </DropdownItem> | </DropdownItem> | ||||
| ); | ); | ||||
| })} | })} | ||||
| </DropdownList> | |||||
| </CheckboxDropdownList> | |||||
| ); | ); | ||||
| }; | }; | ||||
| FilterCheckboxDropdown.defaultProps = { | FilterCheckboxDropdown.defaultProps = { | ||||
| oneValueAllowed: false, | oneValueAllowed: false, | ||||
| }; | }; | ||||
| export default FilterCheckboxDropdown; | export default FilterCheckboxDropdown; |
| 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}; | |||||
| } | |||||
| `; |
| > | > | ||||
| <RadioButton | <RadioButton | ||||
| value={item} | value={item} | ||||
| label={item.name ? item.name : item} | |||||
| label={item?.name ? item?.name : item?.length > 0 ? item : ""} | |||||
| number={item.offerCount} | number={item.offerCount} | ||||
| fullWidth | fullWidth | ||||
| checked={ | checked={ |
| if (props.closeResponsive) props.closeResponsive(); | if (props.closeResponsive) props.closeResponsive(); | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <FilterFooterContainer> | |||||
| <FilterFooterContainer responsiveOpen={props.responsiveOpen}> | |||||
| {props.responsiveOpen && ( | {props.responsiveOpen && ( | ||||
| <PrimaryButton | <PrimaryButton | ||||
| variant="outlined" | variant="outlined" |
| Info, | Info, | ||||
| ButtonsContainer, | ButtonsContainer, | ||||
| PostDate, | PostDate, | ||||
| OfferTitle, | |||||
| OfferDescriptionText, | |||||
| OfferDescriptionTitle, | |||||
| Details, | |||||
| OfferDetails, | |||||
| OfferImage, | |||||
| Scroller, | |||||
| CategoryIcon, | CategoryIcon, | ||||
| SubcategoryIcon, | SubcategoryIcon, | ||||
| QuantityIcon, | QuantityIcon, | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import DeleteOffer from "../OfferCard/DeleteOffer/DeleteOffer"; | import DeleteOffer from "../OfferCard/DeleteOffer/DeleteOffer"; | ||||
| import CreateOffer from "../CreateOfferCard/CreateOffer"; | import CreateOffer from "../CreateOfferCard/CreateOffer"; | ||||
| import OfferDetails from "./OfferDetails/OfferDetails"; | |||||
| const ItemDetailsCard = (props) => { | const ItemDetailsCard = (props) => { | ||||
| const [showModalRemove, setShowModalRemove] = useState(false); | const [showModalRemove, setShowModalRemove] = useState(false); | ||||
| const date = formatDateLocale(new Date(offer?.offer?._created)); | const date = formatDateLocale(new Date(offer?.offer?._created)); | ||||
| const startExchange = () => { | const startExchange = () => { | ||||
| startChat(chats, offer, userId); | |||||
| startChat(chats, offer?.offer, userId); | |||||
| }; | }; | ||||
| const closeEditModalHandler = () => { | const closeEditModalHandler = () => { | ||||
| )} | )} | ||||
| </PostDate> | </PostDate> | ||||
| </OfferInfo> | </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 && ( | {!props.halfwidth && props.showExchangeButton && ( | ||||
| <CheckButton | <CheckButton | ||||
| variant={props.sponsored ? "contained" : "outlined"} | variant={props.sponsored ? "contained" : "outlined"} |
| //import { IconButton } from "../../Buttons/IconButton/IconButton"; | //import { IconButton } from "../../Buttons/IconButton/IconButton"; | ||||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | ||||
| import { Icon } from "../../Icon/Icon"; | import { Icon } from "../../Icon/Icon"; | ||||
| import HorizontalScroller from "../../Scroller/HorizontalScroller"; | |||||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | ||||
| import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg"; | import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg"; | ||||
| import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg"; | import { ReactComponent as Quantity } from "../../../assets/images/svg/quantity.svg"; | ||||
| left: 5px; | 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)` | export const OfferAuthor = styled(Box)` | ||||
| display: flex; | display: flex; | ||||
| flex: 1; | flex: 1; | ||||
| line-height: 16px; | line-height: 16px; | ||||
| font-size: 12px; | 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)` | export const OfferCategory = styled(Box)` | ||||
| font-family: "Open Sans"; | font-family: "Open Sans"; | ||||
| color: ${selectedTheme.primaryText}; | color: ${selectedTheme.primaryText}; | ||||
| `; | `; | ||||
| // export const OfferImage = styled.img` | // 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)` | export const PublishButtonContainer = styled(Box)` | ||||
| display: flex; | display: flex; |
| 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", | |||||
| } |
| 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 ( | 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 = { | OfferDetails.propTypes = { | ||||
| offer: PropTypes.any, | |||||
| } | |||||
| offer: PropTypes.any, | |||||
| showExchangeButton: PropTypes.bool, | |||||
| showPublishButton: PropTypes.bool, | |||||
| }; | |||||
| export default OfferDetails | |||||
| export default OfferDetails; |
| 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; | |||||
| } | |||||
| `; |
| const MessageCard = (props) => { | const MessageCard = (props) => { | ||||
| const message = props.message; | const message = props.message; | ||||
| const dateString = formatDateTime(new Date(message._created)) | |||||
| const dateString = formatDateTime(new Date(message._created)); | |||||
| return ( | return ( | ||||
| <MessageCardContainer isMyMessage={props.isMyMessage}> | <MessageCardContainer isMyMessage={props.isMyMessage}> | ||||
| <ProfileImage src={props.image} /> | <ProfileImage src={props.image} /> | ||||
| <MessageContent isMyMessage={props.isMyMessage}> | <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> | </MessageContent> | ||||
| </MessageCardContainer> | </MessageCardContainer> | ||||
| ); | ); |
| import React, { useState } from "react"; | |||||
| import React, { useMemo, useState } from "react"; | |||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { | import { | ||||
| CheckButton, | CheckButton, | ||||
| import selectedTheme from "../../../themes"; | import selectedTheme from "../../../themes"; | ||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| import CreateOffer from "../CreateOfferCard/CreateOffer"; | import CreateOffer from "../CreateOfferCard/CreateOffer"; | ||||
| import { useSelector } from "react-redux"; | |||||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||||
| const OfferCard = (props) => { | const OfferCard = (props) => { | ||||
| const [deleteOfferModal, setDeleteOfferModal] = useState(false); | const [deleteOfferModal, setDeleteOfferModal] = useState(false); | ||||
| const [editOfferModal, setEditOfferModal] = useState(false); | const [editOfferModal, setEditOfferModal] = useState(false); | ||||
| const history = useHistory(); | const history = useHistory(); | ||||
| const userId = useSelector(selectUserId); | |||||
| const routeToItem = (itemId) => { | const routeToItem = (itemId) => { | ||||
| history.push(`/proizvodi/${itemId}`); | history.push(`/proizvodi/${itemId}`); | ||||
| setEditOfferModal(false); | setEditOfferModal(false); | ||||
| }; | }; | ||||
| const showMessageIcon = useMemo(() => { | |||||
| if (userId === props.offer?.userId) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| }, [userId, props.offer]); | |||||
| if (deleteOfferModal || editOfferModal) { | if (deleteOfferModal || editOfferModal) { | ||||
| document.body.style.overflow = "hidden"; | document.body.style.overflow = "hidden"; | ||||
| } else { | } else { | ||||
| <StarIcon disabled={props.disabledReviews} /> | <StarIcon disabled={props.disabledReviews} /> | ||||
| </StarIconContainer> | </StarIconContainer> | ||||
| ) : ( | ) : ( | ||||
| <MessageIcon vertical={props.vertical} onClick={messageUser}> | |||||
| <MessageIcon | |||||
| showMessageIcon={showMessageIcon} | |||||
| vertical={props.vertical} | |||||
| onClick={messageUser} | |||||
| > | |||||
| <Message /> | <Message /> | ||||
| </MessageIcon> | </MessageIcon> | ||||
| )} | )} |
| } | } | ||||
| `; | `; | ||||
| export const MessageIcon = styled(IconButton)` | export const MessageIcon = styled(IconButton)` | ||||
| ${(props) => !props.showMessageIcon && "display: none;"} | |||||
| width: 40px; | width: 40px; | ||||
| height: 40px; | height: 40px; | ||||
| position: absolute; | position: absolute; | ||||
| `; | `; | ||||
| export const RemoveIcon = styled(Remove)``; | export const RemoveIcon = styled(Remove)``; | ||||
| export const EditIconContainer = styled(MessageIcon)` | export const EditIconContainer = styled(MessageIcon)` | ||||
| display: block; | |||||
| right: 70px; | right: 70px; | ||||
| `; | `; | ||||
| export const EditIcon = styled(Edit)``; | export const EditIcon = styled(Edit)``; | ||||
| export const StarIconContainer = styled(MessageIcon)` | 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; | cursor: initial; | ||||
| & button { | & button { | ||||
| cursor: initial; | cursor: initial; |
| 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" | |||||
| }]; |
| ThumbDown, | ThumbDown, | ||||
| ThumbUp, | ThumbUp, | ||||
| } from "./UserReviewsCard.styled"; | } from "./UserReviewsCard.styled"; | ||||
| import { ListItem } from "@mui/material"; | import { ListItem } from "@mui/material"; | ||||
| import selectedTheme from "../../../themes"; | import selectedTheme from "../../../themes"; | ||||
| import { useTranslation } from "react-i18next"; | 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 UserReviewsCard = (props) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| // const dispatch = useDispatch(); | |||||
| // useEffect(() => { | |||||
| // if (props.review?.userId) { | |||||
| // dispatch(fetchProfile(props.review.userId)); | |||||
| // } | |||||
| // }, [props.review?.userId]) | |||||
| console.log(props); | console.log(props); | ||||
| const review = useMemo(() => { | const review = useMemo(() => { | ||||
| if (props.givingReview) { | if (props.givingReview) { | ||||
| return { | return { | ||||
| ...props.review | |||||
| } | |||||
| ...props.review, | |||||
| }; | |||||
| } | } | ||||
| let isSuccessfulSwap = "DA"; | let isSuccessfulSwap = "DA"; | ||||
| if (props.review.succeeded === "failed") isSuccessfulSwap = "NE"; | if (props.review.succeeded === "failed") isSuccessfulSwap = "NE"; | ||||
| let isGoodCommunication = "DA"; | 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"; | if (props.review.communication === "no") isGoodCommunication = "NE"; | ||||
| return { | return { | ||||
| name: props.review.companyName, | name: props.review.companyName, | ||||
| image: props.review.image, | image: props.review.image, | ||||
| isGoodCommunication, | isGoodCommunication, | ||||
| isSuccessfulSwap, | isSuccessfulSwap, | ||||
| quote: props?.review?.message | |||||
| } | |||||
| quote: props?.review?.message, | |||||
| }; | |||||
| }, [props.review]); | }, [props.review]); | ||||
| const isGood = useMemo(() => { | |||||
| if ( | |||||
| review?.isGoodCommunication === reviewEnum.NO.mainText || | |||||
| review?.isSuccessfulSwap === reviewEnum.NO.mainText | |||||
| ) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| }, [review]); | |||||
| return ( | return ( | ||||
| <ReviewContainer key={review?.image}> | <ReviewContainer key={review?.image}> | ||||
| <ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}> | <ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}> | ||||
| sx={{ pl: 2, py: 2 }} | sx={{ pl: 2, py: 2 }} | ||||
| > | > | ||||
| <ThumbBox item> | <ThumbBox item> | ||||
| {isGood ? <ThumbUp color="success" /> : <ThumbDown color="error" />} | |||||
| {review.isSuccessfulSwap ? ( | |||||
| <ThumbUp color="success" /> | |||||
| ) : ( | |||||
| <ThumbDown color="error" /> | |||||
| )} | |||||
| </ThumbBox> | </ThumbBox> | ||||
| <ReviewQuoteBox item> | <ReviewQuoteBox item> | ||||
| <ReviewQuoteText | |||||
| sx={{ display: "inline" }} | |||||
| component="span" | |||||
| variant="body2" | |||||
| color="text.primary" | |||||
| > | |||||
| "{review?.quote}" | |||||
| </ReviewQuoteText> | |||||
| <ReviewQuoteText>"{review?.quote}"</ReviewQuoteText> | |||||
| </ReviewQuoteBox> | </ReviewQuoteBox> | ||||
| </ReviewQuote> | </ReviewQuote> | ||||
| <ReviewDetails sx={{ pl: 2, pb: 2 }}> | <ReviewDetails sx={{ pl: 2, pb: 2 }}> |
| import React, { useState, useEffect } from "react"; | |||||
| import React, { useState, useEffect } from "react"; | |||||
| import ChatCard from "../Cards/ChatCard/ChatCard"; | import ChatCard from "../Cards/ChatCard/ChatCard"; | ||||
| import { | import { | ||||
| ChatColumnContainer, | ChatColumnContainer, | ||||
| useEffect(() => { | useEffect(() => { | ||||
| dispatch(fetchChats()); | dispatch(fetchChats()); | ||||
| }, []) | |||||
| }, []); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setSortOption(sorting.selectedSortOption); | setSortOption(sorting.selectedSortOption); | ||||
| })} | })} | ||||
| </HeaderSelect> | </HeaderSelect> | ||||
| </TitleSortContainer> | </TitleSortContainer> | ||||
| <ListHeader enableSort={true}></ListHeader> | |||||
| <ListHeader enableSort={true} /> | |||||
| <ListContainer> | <ListContainer> | ||||
| {chats.map((item, index) => ( | {chats.map((item, index) => ( | ||||
| <ChatCard key={index} chat={item} /> | <ChatCard key={index} chat={item} /> |
| } from "./CreateReview.styled"; | } from "./CreateReview.styled"; | ||||
| import FirstStepCreateReview from "./FirstStep/FirstStepCreateReview"; | import FirstStepCreateReview from "./FirstStep/FirstStepCreateReview"; | ||||
| import SecondStepCreateReview from "./SecondStep/SecondStepCreateReview"; | import SecondStepCreateReview from "./SecondStep/SecondStepCreateReview"; | ||||
| import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | import { useDispatch, useSelector } from "react-redux"; | ||||
| import { giveReview } from "../../store/actions/review/reviewActions"; | import { giveReview } from "../../store/actions/review/reviewActions"; | ||||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | import { selectUserId } from "../../store/selectors/loginSelectors"; | ||||
| import { reviewEnum } from "../../enums/reviewEnum"; | import { reviewEnum } from "../../enums/reviewEnum"; | ||||
| import ThirdStepCreateReview from "./ThirdStep/ThirdStepCreateReview"; | |||||
| const CreateReview = (props) => { | const CreateReview = (props) => { | ||||
| const offer = props.offer; | const offer = props.offer; | ||||
| console.log("props aaa: ", props); | |||||
| const [informations, setInformations] = useState({}); | const [informations, setInformations] = useState({}); | ||||
| const [currentStep, setCurrentStep] = useState(1); | const [currentStep, setCurrentStep] = useState(1); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| props.handleGiveReviewSuccess(); | props.handleGiveReviewSuccess(); | ||||
| }; | }; | ||||
| const submitForm = () => { | const submitForm = () => { | ||||
| let communication; | |||||
| if (informations.correctCommunication === reviewEnum.YES.mainText) | |||||
| communication = "yes"; | |||||
| let communication = "yes"; | |||||
| if (informations.correctCommunication === reviewEnum.NO.mainText) | if (informations.correctCommunication === reviewEnum.NO.mainText) | ||||
| communication = "no"; | communication = "no"; | ||||
| if (informations.correctCommunication === reviewEnum.NOT_BAD.mainText) | if (informations.correctCommunication === reviewEnum.NOT_BAD.mainText) | ||||
| communication = "could be better"; | communication = "could be better"; | ||||
| let succeeded; | |||||
| succeeded = "failed"; | |||||
| let succeeded = "failed"; | |||||
| if (informations.exchangeSucceed === reviewEnum.YES.mainText) | if (informations.exchangeSucceed === reviewEnum.YES.mainText) | ||||
| succeeded = "succeeded"; | succeeded = "succeeded"; | ||||
| dispatch( | dispatch( | ||||
| }; | }; | ||||
| const goToNextStep = (newInformations) => { | const goToNextStep = (newInformations) => { | ||||
| setInformations((prevInformations) => { | setInformations((prevInformations) => { | ||||
| console.log({ | |||||
| ...prevInformations, | |||||
| ...newInformations, | |||||
| }); | |||||
| return { | return { | ||||
| ...prevInformations, | ...prevInformations, | ||||
| ...newInformations, | ...newInformations, | ||||
| <CloseButton onClick={closeModal}> | <CloseButton onClick={closeModal}> | ||||
| <CloseIcon /> | <CloseIcon /> | ||||
| </CloseButton> | </CloseButton> | ||||
| {currentStep === 2 ? ( | |||||
| {currentStep === 2 && ( | |||||
| <BackIcon onClick={goToPrevStep}> | <BackIcon onClick={goToPrevStep}> | ||||
| <ArrowBackIcon /> | <ArrowBackIcon /> | ||||
| </BackIcon> | </BackIcon> | ||||
| ) : ( | |||||
| "" | |||||
| )} | )} | ||||
| {currentStep === 1 && ( | {currentStep === 1 && ( | ||||
| <FirstStepCreateReview | <FirstStepCreateReview |
| formik.setFieldValue("exchangeSucceed", event.target.value.mainText) | formik.setFieldValue("exchangeSucceed", event.target.value.mainText) | ||||
| } | } | ||||
| > | > | ||||
| {Object.keys(reviewEnum).map((property) => ( | |||||
| {Object.keys(reviewEnum).map((property) => { | |||||
| if (property === "NOT_BAD") return; | |||||
| return ( | |||||
| <SelectOption | <SelectOption | ||||
| key={reviewEnum[property].value} | key={reviewEnum[property].value} | ||||
| value={reviewEnum[property]} | value={reviewEnum[property]} | ||||
| > | > | ||||
| {reviewEnum[property].mainText} | {reviewEnum[property].mainText} | ||||
| </SelectOption> | </SelectOption> | ||||
| ))} | |||||
| )})} | |||||
| </SelectField> | </SelectField> | ||||
| <FieldLabel leftText={t("reviews.comment")} /> | <FieldLabel leftText={t("reviews.comment")} /> |
| import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle"; | import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle"; | ||||
| import DirectChatHeader from "./DirectChatHeader/DirectChatHeader"; | import DirectChatHeader from "./DirectChatHeader/DirectChatHeader"; | ||||
| import { useDispatch, useSelector } from "react-redux"; | import { useDispatch, useSelector } from "react-redux"; | ||||
| // import { fetchOneOffer } from "../../store/actions/offers/offersActions"; | |||||
| import { useLocation, useRouteMatch } from "react-router-dom"; | import { useLocation, useRouteMatch } from "react-router-dom"; | ||||
| import { fetchOneChat } from "../../store/actions/chat/chatActions"; | |||||
| import { fetchOneChat, setOneChat } from "../../store/actions/chat/chatActions"; | |||||
| import { | import { | ||||
| // selectLatestChats, | |||||
| selectSelectedChat, | selectSelectedChat, | ||||
| } from "../../store/selectors/chatSelectors"; | } from "../../store/selectors/chatSelectors"; | ||||
| import DirectChatContent from "./DirectChatContent/DirectChatContent"; | import DirectChatContent from "./DirectChatContent/DirectChatContent"; | ||||
| const offer = useSelector(selectOffer); | const offer = useSelector(selectOffer); | ||||
| const routeMatch = useRouteMatch(); | const routeMatch = useRouteMatch(); | ||||
| const location = useLocation(); | 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(() => { | const offerObject = useMemo(() => { | ||||
| if (location?.state?.offerId) { | if (location?.state?.offerId) { | ||||
| return offer?.offer; | return offer?.offer; | ||||
| } | } | ||||
| return chat?.offer?.offer; | return chat?.offer?.offer; | ||||
| }, [chat, location.state, offer]); | }, [chat, location.state, offer]); | ||||
| const chatObject = useMemo(() => { | const chatObject = useMemo(() => { | ||||
| if (location?.state?.offerId) { | if (location?.state?.offerId) { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| return chat?.chat; | return chat?.chat; | ||||
| }, [chat, location.state]); | }, [chat, location.state]); | ||||
| const interlocutorObject = useMemo(() => { | const interlocutorObject = useMemo(() => { | ||||
| if (location?.state?.offerId) { | if (location?.state?.offerId) { | ||||
| return { | return { | ||||
| image: offer?.companyData?.image, | image: offer?.companyData?.image, | ||||
| name: offer?.companyData?.company?.name, | name: offer?.companyData?.company?.name, | ||||
| location: offer?.companyData?.company?.contacts?.location | |||||
| } | |||||
| location: offer?.companyData?.company?.contacts?.location, | |||||
| }; | |||||
| } | } | ||||
| return chat?.interlocutor; | 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(() => { | useEffect(() => { | ||||
| console.log(location.state) | |||||
| if (routeMatch.params.idChat && location.state?.offerId) { | |||||
| if (routeMatch.params.idChat) { | |||||
| refreshChat(); | refreshChat(); | ||||
| } | } | ||||
| }, [routeMatch.params.idChat, location.state?.offerId]); | }, [routeMatch.params.idChat, location.state?.offerId]); | ||||
| const refreshChat = () => { | const refreshChat = () => { | ||||
| if (routeMatch.params.idChat === "newMessage") { | if (routeMatch.params.idChat === "newMessage") { | ||||
| dispatch(fetchOneOffer(location.state.offerId)) | |||||
| dispatch(fetchOneOffer(location.state.offerId)); | |||||
| dispatch(setOneChat({})); | |||||
| } else { | } else { | ||||
| dispatch(fetchOneChat(routeMatch.params.idChat)); | dispatch(fetchOneChat(routeMatch.params.idChat)); | ||||
| } | } |
| } | } | ||||
| return false; | return false; | ||||
| }, [exchange, userId]) | }, [exchange, userId]) | ||||
| const refetchExchange = () => { | |||||
| dispatch(fetchExchange(chat.chat.exchangeId)); | |||||
| } | |||||
| const makeReview = () => { | const makeReview = () => { | ||||
| setShowReviewModal(true); | setShowReviewModal(true); | ||||
| }; | }; | ||||
| const handleGiveReviewSuccess = () => { | const handleGiveReviewSuccess = () => { | ||||
| refetchExchange(); | refetchExchange(); | ||||
| } | } | ||||
| const refetchExchange = () => { | |||||
| dispatch(fetchExchange(chat.chat.exchangeId)); | |||||
| } | |||||
| return ( | return ( | ||||
| <DirectChatHeaderContainer> | <DirectChatHeaderContainer> | ||||
| {showReviewModal && ( | {showReviewModal && ( |
| 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 DirectChatHeaderTitle = () => { | ||||
| const {t} = useTranslation(); | |||||
| const { t } = useTranslation(); | |||||
| return ( | return ( | ||||
| <DirectChatHeaderTitleContainer> | <DirectChatHeaderTitleContainer> | ||||
| <MessageIcon /> | |||||
| <HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent> | |||||
| <MessageIcon /> | |||||
| <HeaderTitleContent>{t("messages.headerTitle")}</HeaderTitleContent> | |||||
| </DirectChatHeaderTitleContainer> | </DirectChatHeaderTitleContainer> | ||||
| ) | |||||
| } | |||||
| ); | |||||
| }; | |||||
| DirectChatHeaderTitle.propTypes = { | DirectChatHeaderTitle.propTypes = { | ||||
| children: PropTypes.node, | |||||
| } | |||||
| children: PropTypes.node, | |||||
| }; | |||||
| export default DirectChatHeaderTitle | |||||
| export default DirectChatHeaderTitle; |
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import selectedTheme from "../../../themes"; | import selectedTheme from "../../../themes"; | ||||
| import { useDispatch } from "react-redux"; | 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"; | import { useHistory, useLocation } from "react-router-dom"; | ||||
| const DirectChatNewMessage = (props) => { | const DirectChatNewMessage = (props) => { | ||||
| setTypedValue(""); | setTypedValue(""); | ||||
| }; | }; | ||||
| const handleMessageSendSuccess = (newChatId) => { | const handleMessageSendSuccess = (newChatId) => { | ||||
| console.log("NEW CHAT ID: ", newChatId); | |||||
| history.replace(`${newChatId}`); | history.replace(`${newChatId}`); | ||||
| } | |||||
| }; | |||||
| const initiateNewChat = (typedValue) => { | const initiateNewChat = (typedValue) => { | ||||
| const offerId = location.state.offerId; | const offerId = location.state.offerId; | ||||
| dispatch(startNewChat({offerId, message: typedValue, handleMessageSendSuccess})) | |||||
| } | |||||
| dispatch( | |||||
| startNewChat({ offerId, message: typedValue, handleMessageSendSuccess }) | |||||
| ); | |||||
| }; | |||||
| return ( | return ( | ||||
| <DirectChatNewMessageContainer> | <DirectChatNewMessageContainer> | ||||
| <NewMessageField | <NewMessageField |
| import React, { useEffect, useMemo, useState } from "react"; | |||||
| import React, { useEffect, useMemo } from "react"; | |||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { MiniChatColumnContainer } from "./MiniChatColumn.styled"; | import { MiniChatColumnContainer } from "./MiniChatColumn.styled"; | ||||
| import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard"; | import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard"; | ||||
| const MiniChatColumn = () => { | const MiniChatColumn = () => { | ||||
| const chats = useSelector(selectLatestChats); | const chats = useSelector(selectLatestChats); | ||||
| const [chatsToShow, setChatsToShow] = useState([]); | |||||
| const selectedChat = useSelector(selectSelectedChat); | const selectedChat = useSelector(selectSelectedChat); | ||||
| const [isThereNewChat, setIsThereNewChat] = useState(false); | |||||
| const offer = useSelector(selectOffer); | const offer = useSelector(selectOffer); | ||||
| const location = useLocation(); | const location = useLocation(); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| return { | return { | ||||
| interlocutorData: { | interlocutorData: { | ||||
| image: offer?.companyData?.image, | image: offer?.companyData?.image, | ||||
| name: offer?.companyData?.company?.name | |||||
| name: offer?.companyData?.company?.name, | |||||
| }, | }, | ||||
| offerData: { | 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(() => { | useEffect(() => { | ||||
| dispatch(fetchChats()); | dispatch(fetchChats()); | ||||
| return ( | return ( | ||||
| <MiniChatColumnContainer> | <MiniChatColumnContainer> | ||||
| <MiniChatColumnHeader /> | <MiniChatColumnHeader /> | ||||
| {isThereNewChat && ( | |||||
| <MiniChatCard | |||||
| chat={newChat} | |||||
| selected | |||||
| /> | |||||
| )} | |||||
| {chatsToShow.map((item) => { | |||||
| {location.state?.offerId && <MiniChatCard chat={newChat} selected />} | |||||
| {chats.map((item) => { | |||||
| return ( | 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> | </MiniChatColumnContainer> | ||||
| ); | ); | ||||
| }; | }; |
| 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 MiniChatColumnHeader = () => { | ||||
| const {t} = useTranslation(); | |||||
| const { t } = useTranslation(); | |||||
| return ( | return ( | ||||
| <MiniChatColumnHeaderContainer> | <MiniChatColumnHeaderContainer> | ||||
| <MailIcon/> | |||||
| <HeaderTitleContent>{t("messages.miniChatHeaderTitle")}</HeaderTitleContent> | |||||
| <MailIcon /> | |||||
| <HeaderTitleContent> | |||||
| {t("messages.miniChatHeaderTitle")} | |||||
| </HeaderTitleContent> | |||||
| </MiniChatColumnHeaderContainer> | </MiniChatColumnHeaderContainer> | ||||
| ) | |||||
| } | |||||
| ); | |||||
| }; | |||||
| MiniChatColumnHeader.propTypes = { | MiniChatColumnHeader.propTypes = { | ||||
| children: PropTypes.node, | |||||
| } | |||||
| children: PropTypes.node, | |||||
| }; | |||||
| export default MiniChatColumnHeader | |||||
| export default MiniChatColumnHeader; |
| const DropdownList = (props) => { | const DropdownList = (props) => { | ||||
| const [listShown, setListShown] = useState(props.defaultOpen); | const [listShown, setListShown] = useState(props.defaultOpen); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (props.open !== null || props.open !== undefined) { | |||||
| if (props.open !== null || props.open !== undefined) | |||||
| setListShown(props.open); | setListShown(props.open); | ||||
| } | |||||
| }, [props.open]); | }, [props.open]); | ||||
| const handleShow = () => { | const handleShow = () => { | ||||
| if (props.setIsOpened) { | |||||
| if (props.setIsOpened) | |||||
| props.setIsOpened(!listShown); | props.setIsOpened(!listShown); | ||||
| } | |||||
| if (!props.disabled) { | |||||
| if (!props.disabled) | |||||
| setListShown((prevState) => !prevState); | |||||
| if (!(props.open !== null || props.open !== undefined)) | |||||
| setListShown((prevState) => !prevState); | setListShown((prevState) => !prevState); | ||||
| } | |||||
| if (!(props.open !== null || props.open !== undefined)) { | |||||
| setListShown(prevState => !prevState) | |||||
| } | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | <DropdownListContainer fullwidth={props.fullWidth ? 1 : 0}> | ||||
| > | > | ||||
| {props.title} | {props.title} | ||||
| </DropdownTitle> | </DropdownTitle> | ||||
| {(props.open !== null && props.open !== undefined ? props.open : listShown) ? ( | |||||
| {( | |||||
| props.open !== null && props.open !== undefined | |||||
| ? props.open | |||||
| : listShown | |||||
| ) ? ( | |||||
| <ToggleIconOpened | <ToggleIconOpened | ||||
| style={props.toggleIconStyles} | style={props.toggleIconStyles} | ||||
| onClick={!props.disabled ? () => handleShow() : () => {}} | onClick={!props.disabled ? () => handleShow() : () => {}} | ||||
| </ToggleIconClosed> | </ToggleIconClosed> | ||||
| )} | )} | ||||
| </DropdownHeader> | </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> | <DropdownOptions>{props.headerOptions}</DropdownOptions> | ||||
| <ListContainer>{props.children}</ListContainer> | <ListContainer>{props.children}</ListContainer> | ||||
| </ToggleContainer> | </ToggleContainer> | ||||
| </DropdownListContainer> | </DropdownListContainer> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default DropdownList; | export default DropdownList; | ||||
| DropdownList.propTypes = { | DropdownList.propTypes = { | ||||
| title: PropTypes.string, | title: PropTypes.string, | ||||
| dropdownIcon: PropTypes.node, | dropdownIcon: PropTypes.node, | ||||
| open: PropTypes.bool, | open: PropTypes.bool, | ||||
| disabled: PropTypes.bool, | disabled: PropTypes.bool, | ||||
| }; | }; | ||||
| DropdownList.defaultProps = { | DropdownList.defaultProps = { | ||||
| fullWidth: false, | fullWidth: false, | ||||
| defaultOpen: false, | defaultOpen: false, |
| ToolsContainer, | ToolsContainer, | ||||
| UserIcon, | UserIcon, | ||||
| } from "./Drawer.styled"; | } from "./Drawer.styled"; | ||||
| import { useSelector } from "react-redux"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | |||||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | import { selectUserId } from "../../../store/selectors/loginSelectors"; | ||||
| import selectedTheme from "../../../themes"; | import selectedTheme from "../../../themes"; | ||||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | import { IconButton } from "../../Buttons/IconButton/IconButton"; | ||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| import { CHAT_PAGE, LOGIN_PAGE, MY_OFFERS_PAGE, REGISTER_PAGE } from "../../../constants/pages"; | import { CHAT_PAGE, LOGIN_PAGE, MY_OFFERS_PAGE, REGISTER_PAGE } from "../../../constants/pages"; | ||||
| import { selectProfileName } from "../../../store/selectors/profileSelectors"; | import { selectProfileName } from "../../../store/selectors/profileSelectors"; | ||||
| import { logoutUser } from "../../../store/actions/login/loginActions"; | |||||
| export const Drawer = (props) => { | export const Drawer = (props) => { | ||||
| const user = useSelector(selectUserId); | const user = useSelector(selectUserId); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const history = useHistory(); | const history = useHistory(); | ||||
| const name = useSelector(selectProfileName); | const name = useSelector(selectProfileName); | ||||
| const dispatch = useDispatch(); | |||||
| const goToMyPosts = () => { | const goToMyPosts = () => { | ||||
| props.toggleDrawer(); | props.toggleDrawer(); | ||||
| props.toggleDrawer(); | props.toggleDrawer(); | ||||
| props.addOffer(); | props.addOffer(); | ||||
| } | } | ||||
| const logoutUser = () => {}; | |||||
| const logout = () => { | |||||
| props.toggleDrawer(); | |||||
| dispatch(logoutUser()); | |||||
| }; | |||||
| return ( | return ( | ||||
| <DrawerContainer> | <DrawerContainer> | ||||
| <CloseButton onClick={props.toggleDrawer}> | <CloseButton onClick={props.toggleDrawer}> | ||||
| {t("header.addOffer")} | {t("header.addOffer")} | ||||
| </AddOfferButton> | </AddOfferButton> | ||||
| <LogoutButton> | <LogoutButton> | ||||
| <IconButton onClick={logoutUser}> | |||||
| <IconButton onClick={logout}> | |||||
| <LogoutIcon /> | <LogoutIcon /> | ||||
| </IconButton> | </IconButton> | ||||
| <LogoutText>{t("common.logout")}</LogoutText> | <LogoutText>{t("common.logout")}</LogoutText> |
| searchRef.current.removeEventListener("keyup", listener); | searchRef.current.removeEventListener("keyup", listener); | ||||
| }; | }; | ||||
| const handleSearch = (value) => { | const handleSearch = (value) => { | ||||
| if (value.length === 0) return; | |||||
| search.searchOffers(value); | search.searchOffers(value); | ||||
| }; | }; | ||||
| const toggleFilters = () => { | const toggleFilters = () => { |
| import React, { useEffect, useRef, useState } from "react"; | |||||
| import React, { useCallback, useEffect, useRef, useState } from "react"; | |||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { | import { | ||||
| AddFile, | AddFile, | ||||
| import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg"; | import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg"; | ||||
| import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg"; | import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg"; | ||||
| // import { Input } from "@mui/material"; | |||||
| const ImagePicker = (props) => { | const ImagePicker = (props) => { | ||||
| const fileInputRef = useRef(null); | const fileInputRef = useRef(null); | ||||
| const imageRef = useRef(null); | const imageRef = useRef(null); | ||||
| const [isEditing, setIsEditing] = useState(false); | const [isEditing, setIsEditing] = useState(false); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log("image", props); | |||||
| if (props.image) { | |||||
| if (props.image) | |||||
| setImage(props.image); | setImage(props.image); | ||||
| } | |||||
| }, [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); | window.addEventListener("click", listener); | ||||
| return () => window.removeEventListener("click", listener); | return () => window.removeEventListener("click", listener); | ||||
| }, [imageRef]); | |||||
| }, []); | |||||
| const handleChange = () => { | const handleChange = () => { | ||||
| fileInputRef.current.value = ""; | fileInputRef.current.value = ""; | ||||
| fileInputRef.current.click(); | fileInputRef.current.click(); | ||||
| console.log(error); | console.log(error); | ||||
| }; | }; | ||||
| }; | }; | ||||
| const handleDelete = () => { | const handleDelete = () => { | ||||
| if (props.deleteImage) props.deleteImage(); | if (props.deleteImage) props.deleteImage(); | ||||
| setImage(""); | setImage(""); | ||||
| </ImagePickerContainer> | </ImagePickerContainer> | ||||
| ); | ); | ||||
| }; | }; | ||||
| ImagePicker.propTypes = { | ImagePicker.propTypes = { | ||||
| children: PropTypes.node, | children: PropTypes.node, | ||||
| className: PropTypes.string, | className: PropTypes.string, | ||||
| deleteImage: PropTypes.func, | deleteImage: PropTypes.func, | ||||
| showDeleteIcon: PropTypes.bool, | showDeleteIcon: PropTypes.bool, | ||||
| }; | }; | ||||
| export default ImagePicker; | export default ImagePicker; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; |
| import React from "react"; | import React from "react"; | ||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| //import { IconButton } from "../../Buttons/IconButton/IconButton"; | |||||
| import { HeaderContainer, HeaderText, ButtonContainer } from "./Header.styled"; | import { HeaderContainer, HeaderText, ButtonContainer } from "./Header.styled"; | ||||
| import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton"; | import { ArrowButton } from "../../Buttons/ArrowButton/ArrowButton"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| > | > | ||||
| <ButtonContainer> | <ButtonContainer> | ||||
| <ArrowButton side={"left"}></ArrowButton> | <ArrowButton side={"left"}></ArrowButton> | ||||
| <HeaderText>{t("profile.back")}</HeaderText> | |||||
| {/* <HeaderText>{t("profile.back")}</HeaderText> */} | |||||
| <HeaderText>{t("itemDetailsCard.headerTitle")}</HeaderText> | |||||
| </ButtonContainer> | </ButtonContainer> | ||||
| </HeaderContainer> | </HeaderContainer> | ||||
| ); | ); |
| import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard"; | import ItemDetailsHeaderCard from "./ItemDetailsHeaderCard/ItemDetailsHeaderCard"; | ||||
| import { selectOffer } from "../../store/selectors/offersSelectors"; | import { selectOffer } from "../../store/selectors/offersSelectors"; | ||||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | import { selectUserId } from "../../store/selectors/loginSelectors"; | ||||
| // import { useHistory } from 'react-router-dom'; | |||||
| const ItemDetails = () => { | const ItemDetails = () => { | ||||
| const offer = useSelector(selectOffer); | const offer = useSelector(selectOffer); | ||||
| const userId = useSelector(selectUserId); | const userId = useSelector(selectUserId); | ||||
| let isMyProfile = useMemo(() => { | let isMyProfile = useMemo(() => { | ||||
| if (offer?.offer?.userId?.toString() === userId.toString()) { | |||||
| if (offer?.offer?.userId?.toString() === userId?.toString()) { | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; |
| import React from "react"; | import React from "react"; | ||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import { | import { | ||||
| DetailIcon, | |||||
| DetailText, | |||||
| MessageIcon, | MessageIcon, | ||||
| OfferDetails, | OfferDetails, | ||||
| OfferImage, | OfferImage, | ||||
| OfferTitle, | OfferTitle, | ||||
| DetailContainer, | |||||
| HeaderTop, | HeaderTop, | ||||
| HeaderDetails, | |||||
| BottomDetails, | |||||
| StatusText, | |||||
| PIBIcon, | |||||
| UserIcon, | UserIcon, | ||||
| UserIconContainer, | UserIconContainer, | ||||
| } from "./ItemDetailsHeaderCard.styled"; | } from "./ItemDetailsHeaderCard.styled"; | ||||
| import { ItemDetailsHeaderContainer } 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 { ReactComponent as MessageColor } from "../../../assets/images/svg/mailColor.svg"; | ||||
| import selectedTheme from "../../../themes"; | |||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| import { useSelector } from "react-redux"; | import { useSelector } from "react-redux"; | ||||
| import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | ||||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | 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 ItemDetailsHeaderCard = (props) => { | ||||
| const history = useHistory(); | const history = useHistory(); | ||||
| const chats = useSelector(selectLatestChats); | const chats = useSelector(selectLatestChats); | ||||
| const offer = props.offer; | const offer = props.offer; | ||||
| const userId = useSelector(selectUserId); | 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 = () => { | const handleGoProfile = () => { | ||||
| history.push(`/profile/${offer?.offer?.userId}`); | history.push(`/profile/${offer?.offer?.userId}`); | ||||
| }; | }; | ||||
| <OfferTitle isMyProfile={props.isMyProfile} onClick={handleGoProfile}> | <OfferTitle isMyProfile={props.isMyProfile} onClick={handleGoProfile}> | ||||
| {offer?.companyData?.company?.name} | {offer?.companyData?.company?.name} | ||||
| </OfferTitle> | </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> | </OfferDetails> | ||||
| {props.isMyProfile ? ( | {props.isMyProfile ? ( | ||||
| <UserIconContainer onClick={handleGoProfile}> | <UserIconContainer onClick={handleGoProfile}> | ||||
| </MessageIcon> | </MessageIcon> | ||||
| )} | )} | ||||
| </HeaderTop> | </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> | </ItemDetailsHeaderContainer> | ||||
| ); | ); | ||||
| }; | }; |
| import { Box, Grid, Typography } from "@mui/material"; | |||||
| import { Box, Typography } from "@mui/material"; | |||||
| import styled from "styled-components"; | import styled from "styled-components"; | ||||
| import selectedTheme from "../../../themes"; | import selectedTheme from "../../../themes"; | ||||
| import { IconButton } from "../../Buttons/IconButton/IconButton"; | import { IconButton } from "../../Buttons/IconButton/IconButton"; | ||||
| import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; | ||||
| import { Icon } from "../../Icon/Icon"; | |||||
| import { ReactComponent as User} from "../../../assets/images/svg/user.svg"; | import { ReactComponent as User} from "../../../assets/images/svg/user.svg"; | ||||
| max-width: 2000px; | max-width: 2000px; | ||||
| position: relative; | 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)` | export const HeaderTop = styled(Box)` | ||||
| display: flex; | display: flex; | ||||
| flex-direction: row; | flex-direction: row; | ||||
| padding: 18px; | padding: 18px; | ||||
| gap: 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` | export const OfferImage = styled.img` | ||||
| border-radius: 50%; | border-radius: 50%; | ||||
| width: 144px; | width: 144px; | ||||
| `; | `; | ||||
| export const StatusText = styled(Grid)` | |||||
| font-family: "Open Sans"; | |||||
| color: ${selectedTheme.primaryText}; | |||||
| @media (max-width: 600px) { | |||||
| font-size: 12px; | |||||
| } | |||||
| `; | |||||
| export const OfferCategory = styled(Box)` | export const OfferCategory = styled(Box)` | ||||
| font-family: "Open Sans"; | font-family: "Open Sans"; | ||||
| color: ${selectedTheme.primaryText}; | color: ${selectedTheme.primaryText}; | ||||
| width: 0; | width: 0; | ||||
| margin: auto 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)` | export const CheckButton = styled(PrimaryButton)` | ||||
| width: 180px; | width: 180px; | ||||
| height: 48px; | height: 48px; | ||||
| } | } | ||||
| } | } | ||||
| `; | `; | ||||
| 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)` | export const UserIconContainer = styled(MessageIcon)` | ||||
| background-color: ${selectedTheme.primaryIconBackgroundColor}; | background-color: ${selectedTheme.primaryIconBackgroundColor}; | ||||
| ` | ` |
| 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; |
| 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; | |||||
| } | |||||
| `; |
| 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; |
| 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; | |||||
| } | |||||
| } | |||||
| `; |
| 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; |
| 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; | |||||
| ` |
| 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", | |||||
| } |
| 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; |
| 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; |
| 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; | |||||
| ` |
| 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; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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; |
| 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; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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; |
| 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; | |||||
| } | |||||
| `; |
| 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; |
| 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; | |||||
| `; |
| 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; |
| 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 = { | 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; | export default DrawerComponent; |