| { | { | ||||
| "name": "web", | "name": "web", | ||||
| "version": "4.0.6", | |||||
| "version": "4.0.7", | |||||
| "private": true, | "private": true, | ||||
| "dependencies": { | "dependencies": { | ||||
| "@emotion/react": "^11.5.0", | "@emotion/react": "^11.5.0", |
| <link | <link | ||||
| rel="stylesheet" | rel="stylesheet" | ||||
| type="text/css" | type="text/css" | ||||
| href="https://fonts.googleapis.com/css?family=Open+Sans" | |||||
| href="https://fonts.googleapis.com/css?family=Open+Sans:wght@700;600;500;400;300" | |||||
| /> | /> | ||||
| <link | <link | ||||
| rel="stylesheet" | rel="stylesheet" |
| <svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path d="M0 9.7972L1.8 9.79834C2.29922 9.79834 2.7 9.39643 2.7 8.89749V2.60845H0V9.7972ZM1.35 8.00283C1.59609 8.00283 1.8 8.20302 1.8 8.45254C1.8 8.69841 1.59609 8.90226 1.35 8.90226C1.10391 8.90226 0.9 8.69751 0.9 8.45283C0.9 8.20251 1.10391 8.00283 1.35 8.00283ZM9.81 0.800014C9.58666 0.800014 9.36956 0.883517 9.20475 1.03424L6.43781 3.5647C6.435 3.57033 6.435 3.57595 6.42938 3.57595C5.9625 4.0147 5.97094 4.71501 6.37031 5.15095C6.72891 5.51939 7.47787 5.64651 7.94897 5.22829C7.95375 5.22408 7.95656 5.22408 7.95937 5.22126L10.206 3.16533C10.3888 3.0002 10.6771 3.01075 10.8422 3.19345C11.0109 3.37615 10.9969 3.66089 10.8141 3.82964L10.0794 4.50098L14.175 7.82283C14.2559 7.89303 14.3297 7.96334 14.3965 8.04057V2.5747C13.2439 1.4227 11.683 0.778076 10.054 0.778076L9.81 0.800014ZM9.41062 5.11439L8.56687 5.88754C7.73016 6.65001 6.45047 6.57633 5.70375 5.76461C4.95 4.94001 5.00906 3.66033 5.83031 2.90658L8.13094 0.800014H7.94531C6.31969 0.800014 4.75594 1.44914 3.60562 2.5972L3.6 8.89439L4.11328 8.89543L6.65859 11.1966C7.43203 11.8258 8.56406 11.7062 9.18984 10.9366L9.69947 11.3652C10.1461 11.7055 10.807 11.6605 11.1727 11.2141L12.0552 10.1285L12.2064 10.255C12.5931 10.5644 13.1592 10.5082 13.472 10.1215L13.7402 9.79017C14.053 9.40345 13.9944 8.84011 13.6082 8.52651L9.41062 5.11439ZM15.3 2.61126V8.90001C15.3 9.39558 15.7008 9.80142 16.1747 9.80142L18 9.80001V2.60283L15.3 2.61126ZM16.65 8.90001C16.4039 8.90001 16.2 8.69622 16.2 8.45029C16.2 8.20069 16.4039 8.00058 16.65 8.00058C16.8961 8.00058 17.1 8.20251 17.1 8.45283C17.1 8.69751 16.8975 8.90001 16.65 8.90001Z" fill="#5A3984"/> | |||||
| </svg> |
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||||
| <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#C4C4C4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/> | |||||
| <path d="M12 16V12" stroke="#C4C4C4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/> | |||||
| <path d="M12 8H12.01" stroke="#C4C4C4" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/> | |||||
| </svg> |
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { FieldLabel, SelectOption } from "../FirstPartCreateOffer.styled"; | import { FieldLabel, SelectOption } from "../FirstPartCreateOffer.styled"; | ||||
| import { SelectField } from "../../CreateOffer.styled"; | import { SelectField } from "../../CreateOffer.styled"; | ||||
| import { CategoryContainer, CategoryIcon } from "./OfferCategoryField.styled"; | |||||
| import { | |||||
| getImageUrl, | |||||
| variants, | |||||
| } from "../../../../../util/helpers/imageUrlGetter"; | |||||
| import useIsMobile from "../../../../../hooks/useIsMobile"; | |||||
| import { CategoryContainer } from "./OfferCategoryField.styled"; | |||||
| // import { | |||||
| // getImageUrl, | |||||
| // variants, | |||||
| // } from "../../../../../util/helpers/imageUrlGetter"; | |||||
| // import useIsMobile from "../../../../../hooks/useIsMobile"; | |||||
| const OfferCategoryField = (props) => { | const OfferCategoryField = (props) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const { isMobile } = useIsMobile(); | |||||
| // const { isMobile } = useIsMobile(); | |||||
| const formik = props.formik; | const formik = props.formik; | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| onClick={() => props.handleSubcategories(cat.name)} | onClick={() => props.handleSubcategories(cat.name)} | ||||
| > | > | ||||
| <CategoryContainer> | <CategoryContainer> | ||||
| <CategoryIcon | |||||
| {/* <CategoryIcon | |||||
| src={getImageUrl(cat.image, variants.categoryIcon, isMobile)} | src={getImageUrl(cat.image, variants.categoryIcon, isMobile)} | ||||
| /> | |||||
| /> */} | |||||
| {cat.name} | {cat.name} | ||||
| </CategoryContainer> | </CategoryContainer> | ||||
| </SelectOption> | </SelectOption> |
| export const MessageCardContainer = styled(Box)` | export const MessageCardContainer = styled(Box)` | ||||
| display: flex; | display: flex; | ||||
| flex-direction: ${props => props.ismymessage ? `row-reverse` : `row`}; | |||||
| flex-direction: ${(props) => (props.ismymessage ? `row-reverse` : `row`)}; | |||||
| margin-bottom: 18px; | margin-bottom: 18px; | ||||
| `; | `; | ||||
| export const ProfileImage = styled.img` | export const ProfileImage = styled.img` | ||||
| } | } | ||||
| `; | `; | ||||
| export const MessageContent = styled(Box)` | export const MessageContent = styled(Box)` | ||||
| background-color: ${(props) => | |||||
| props.ismymessage | |||||
| ? selectedTheme.colors.primaryPurple | |||||
| : selectedTheme.colors.messageBackground}; | |||||
| background-color: ${selectedTheme.colors.messageBackground}; | |||||
| border-radius: ${(props) => | border-radius: ${(props) => | ||||
| props.ismymessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"}; | props.ismymessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"}; | ||||
| padding: 9px; | |||||
| padding-bottom: 31px; | |||||
| position: relative; | |||||
| min-height: 65px; | |||||
| margin: 0 18px; | |||||
| min-width: 110px; | |||||
| @media (max-width: 600px) { | |||||
| width: 100%; | |||||
| } | |||||
| padding: 9px; | |||||
| padding-bottom: 31px; | |||||
| position: relative; | |||||
| min-height: 65px; | |||||
| margin: 0 18px; | |||||
| min-width: 110px; | |||||
| @media (max-width: 600px) { | |||||
| width: 100%; | |||||
| } | |||||
| `; | `; | ||||
| export const MessageText = styled(Typography)` | export const MessageText = styled(Typography)` | ||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| font-size: 16px; | |||||
| line-height: 22px; | |||||
| color: ${props => props.ismymessage ? `white` : selectedTheme.colors.messageText}; | |||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| font-size: 16px; | |||||
| line-height: 22px; | |||||
| color: ${selectedTheme.colors.messageText}; | |||||
| `; | `; | ||||
| export const MessageDate = styled(Typography)` | export const MessageDate = styled(Typography)` | ||||
| color: ${props => props.ismymessage ? selectedTheme.colors.messageMyDate : selectedTheme.colors.messageDate}; | |||||
| font-size: 12px; | |||||
| font-style: italic; | |||||
| position: absolute; | |||||
| bottom: 9px; | |||||
| left: 9px; | |||||
| color: ${selectedTheme.colors.messageDate}; | |||||
| font-size: 12px; | |||||
| font-style: italic; | |||||
| position: absolute; | |||||
| bottom: 9px; | |||||
| left: 9px; | |||||
| `; | `; |
| ButtonsContainer, | ButtonsContainer, | ||||
| TooltipInnerContainer, | TooltipInnerContainer, | ||||
| UnpinOutlinedIcon, | UnpinOutlinedIcon, | ||||
| AcceptIconContainer, | |||||
| AcceptIcon, | |||||
| } from "./OfferCard.styled"; | } from "./OfferCard.styled"; | ||||
| import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg"; | import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg"; | ||||
| import { useHistory } from "react-router-dom"; | import { useHistory } from "react-router-dom"; | ||||
| ADMIN_ITEM_DETAILS_PAGE, | ADMIN_ITEM_DETAILS_PAGE, | ||||
| ITEM_DETAILS_PAGE, | ITEM_DETAILS_PAGE, | ||||
| } from "../../../constants/pages"; | } from "../../../constants/pages"; | ||||
| import exchangeStatus from "../../../constants/exchangeStatus"; | |||||
| const OfferCard = (props) => { | const OfferCard = (props) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| props.messageUser(props.offer); | props.messageUser(props.offer); | ||||
| }; | }; | ||||
| const makeReview = (event) => { | const makeReview = (event) => { | ||||
| console.log("EVEEEEEEENT: ", event); | |||||
| event.stopPropagation(); | event.stopPropagation(); | ||||
| if (!props.disabledReviews) { | if (!props.disabledReviews) { | ||||
| props.makeReview(props.offer); | props.makeReview(props.offer); | ||||
| ); | ); | ||||
| }; | }; | ||||
| const acceptExchange = (event) => { | |||||
| event.stopPropagation(); | |||||
| console.log("props exchange"); | |||||
| props.acceptExchange(); | |||||
| }; | |||||
| const showMessageIcon = useMemo(() => { | const showMessageIcon = useMemo(() => { | ||||
| if (userId === props.offer?.userId) { | if (userId === props.offer?.userId) { | ||||
| return false; | return false; | ||||
| </Tooltip> | </Tooltip> | ||||
| </> | </> | ||||
| ) : props.aboveChat ? ( | ) : props.aboveChat ? ( | ||||
| <LikeIconContainer | |||||
| customDisabled={props.disabledReviews} | |||||
| disabled={props.disabledReviews} | |||||
| onClick={makeReview} | |||||
| > | |||||
| <LikeIcon disabled={props.disabledReviews} /> | |||||
| </LikeIconContainer> | |||||
| props.exchangeState === exchangeStatus.ACCEPTED || | |||||
| props.exchangeState === exchangeStatus.REVIEWED ? ( | |||||
| <LikeIconContainer | |||||
| customDisabled={props.disabledReviews} | |||||
| disabled={props.disabledReviews} | |||||
| onClick={makeReview} | |||||
| > | |||||
| <LikeIcon disabled={props.disabledReviews} /> | |||||
| </LikeIconContainer> | |||||
| ) : ( | |||||
| <AcceptIconContainer // MENJATI | |||||
| customDisabled={ | |||||
| props.exchangeState === exchangeStatus.I_OFFERED | |||||
| } | |||||
| disabled={props.exchangeState === exchangeStatus.I_OFFERED} | |||||
| onClick={acceptExchange} | |||||
| > | |||||
| <AcceptIcon | |||||
| disabled={props.exchangeState === exchangeStatus.I_OFFERED} | |||||
| /> | |||||
| </AcceptIconContainer> | |||||
| ) | |||||
| ) : ( | ) : ( | ||||
| <Tooltip title={t("messages.tooltip")}> | <Tooltip title={t("messages.tooltip")}> | ||||
| <TooltipInnerContainer> | <TooltipInnerContainer> | ||||
| skeleton: PropTypes.bool, | skeleton: PropTypes.bool, | ||||
| disabledCheckButton: PropTypes.bool, | disabledCheckButton: PropTypes.bool, | ||||
| isAdmin: PropTypes.bool, | isAdmin: PropTypes.bool, | ||||
| exchangeState: PropTypes.any, | |||||
| acceptExchange: PropTypes.func, | |||||
| }; | }; | ||||
| OfferCard.defaultProps = { | OfferCard.defaultProps = { | ||||
| halfwidth: false, | halfwidth: false, |
| import { ReactComponent as UnpinOutlined } from "../../../assets/images/svg/unpin-outlined.svg"; | import { ReactComponent as UnpinOutlined } from "../../../assets/images/svg/unpin-outlined.svg"; | ||||
| import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | import { ReactComponent as Category } from "../../../assets/images/svg/category.svg"; | ||||
| import { ReactComponent as Calendar } from "../../../assets/images/svg/calendar.svg"; | import { ReactComponent as Calendar } from "../../../assets/images/svg/calendar.svg"; | ||||
| import { ReactComponent as Accept } from "../../../assets/images/svg/accept.svg"; | |||||
| export const OfferCardContainer = styled(Container)` | export const OfferCardContainer = styled(Container)` | ||||
| display: ${(props) => (props.skeleton ? "none" : "flex")}; | display: ${(props) => (props.skeleton ? "none" : "flex")}; | ||||
| } | } | ||||
| `} | `} | ||||
| `; | `; | ||||
| export const AcceptIconContainer = styled(MessageIcon)` | |||||
| display: block; | |||||
| opacity: ${(props) => (props.disabled ? "0.4" : "1")}; | |||||
| ${(props) => | |||||
| props.disabled && | |||||
| ` | |||||
| cursor: initial; | |||||
| & button { | |||||
| cursor: initial; | |||||
| } | |||||
| `} | |||||
| `; | |||||
| export const PinIconContainer = styled(MessageIcon)` | export const PinIconContainer = styled(MessageIcon)` | ||||
| @media (max-width: 600px) { | @media (max-width: 600px) { | ||||
| ${(props) => | ${(props) => | ||||
| top: -1px; | top: -1px; | ||||
| } | } | ||||
| `; | `; | ||||
| export const AcceptIcon = styled(Accept)` | |||||
| & path { | |||||
| fill: ${(props) => | |||||
| props.disabled | |||||
| ? selectedTheme.colors.primaryPurpleDisabled | |||||
| : selectedTheme.colors.primaryPurple}; | |||||
| } | |||||
| @media (max-width: 600px) { | |||||
| position: relative; | |||||
| top: -1px; | |||||
| } | |||||
| `; | |||||
| export const PinOutlinedIcon = styled(PinOutlined)` | export const PinOutlinedIcon = styled(PinOutlined)` | ||||
| & g path { | & g path { | ||||
| /* fill: transparent !important; */ | /* fill: transparent !important; */ |
| import React from "react"; | |||||
| import PropTypes from "prop-types"; | |||||
| import { | |||||
| InfoIcon, | |||||
| MessageContent, | |||||
| MessageDate, | |||||
| MessageText, | |||||
| RequestExchangeCardContainer, | |||||
| } from "./RequestExchangeCard.styled"; | |||||
| import { getMessageDate } from "../../../util/helpers/dateHelpers"; | |||||
| import { useSelector } from "react-redux"; | |||||
| import { | |||||
| selectExchange, | |||||
| selectRequester, | |||||
| } from "../../../store/selectors/exchangeSelector"; | |||||
| import { useMemo } from "react"; | |||||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||||
| import requesterStatus from "../../../constants/requesterStatus"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import RequestExchangeMessage from "./RequestExchangeMessage/RequestExchangeMessage"; | |||||
| const RequestExchangeCard = (props) => { | |||||
| const dateString = getMessageDate(new Date(props?.message?._created)); | |||||
| const { t } = useTranslation(); | |||||
| const userId = useSelector(selectUserId); | |||||
| const requester = useSelector(selectRequester); | |||||
| const exchange = useSelector(selectExchange); | |||||
| const amIBuyer = useMemo( | |||||
| () => exchange?.buyer?.userId === userId, | |||||
| [exchange, userId] | |||||
| ); | |||||
| console.log("requester", requester); | |||||
| console.log("exchange: ", exchange); | |||||
| const haveIAccepted = useMemo( | |||||
| () => (amIBuyer ? exchange?.buyer?.accepted : exchange?.seller?.accepted), | |||||
| [amIBuyer, exchange] | |||||
| ); | |||||
| console.log(haveIAccepted) | |||||
| const interlucatorUserId = useMemo( | |||||
| () => (amIBuyer ? exchange?.seller?.userId : exchange?.buyer?.userId), | |||||
| [exchange, amIBuyer] | |||||
| ); | |||||
| const message = useMemo(() => { | |||||
| if (requester === requesterStatus.ME) { | |||||
| if (props.isMyMessage) { | |||||
| return ( | |||||
| <MessageText ismymessage={props.isMyMessage}> | |||||
| {t("messages.requestSent")} | |||||
| </MessageText> | |||||
| ); | |||||
| } else { | |||||
| return ( | |||||
| <MessageText ismymessage={props.isMyMessage}> | |||||
| {t("messages.requestAccepted")} | |||||
| </MessageText> | |||||
| ); | |||||
| } | |||||
| } else if (requester === requesterStatus.INTERLUCATOR) { | |||||
| return ( | |||||
| <RequestExchangeMessage | |||||
| haveIAccepted={haveIAccepted} | |||||
| chatId={props.chatId} | |||||
| userId={userId} | |||||
| interlucatorUserId={interlucatorUserId} | |||||
| /> | |||||
| ); | |||||
| } | |||||
| return ""; | |||||
| }, [requester, t, haveIAccepted, interlucatorUserId, userId, exchange]); | |||||
| return ( | |||||
| <RequestExchangeCardContainer ismymessage={props.isMyMessage}> | |||||
| <InfoIcon /> | |||||
| <MessageContent ismymessage={props.isMyMessage}> | |||||
| {message} | |||||
| <MessageDate ismymessage={props.isMyMessage}>{dateString}</MessageDate> | |||||
| </MessageContent> | |||||
| </RequestExchangeCardContainer> | |||||
| ); | |||||
| }; | |||||
| RequestExchangeCard.propTypes = { | |||||
| isMyMessage: PropTypes.bool, | |||||
| message: PropTypes.any, | |||||
| chatId: PropTypes.string, | |||||
| }; | |||||
| export default RequestExchangeCard; |
| import { Box, Typography } from "@mui/material"; | |||||
| import styled from "styled-components"; | |||||
| import { ReactComponent as Info } from "../../../assets/images/svg/info-circled.svg"; | |||||
| import selectedTheme from "../../../themes"; | |||||
| export const RequestExchangeCardContainer = styled(Box)` | |||||
| display: flex; | |||||
| flex-direction: ${(props) => (props.ismymessage ? `row-reverse` : `row`)}; | |||||
| margin-bottom: 18px; | |||||
| `; | |||||
| export const MessageContent = styled(Box)` | |||||
| background-color: ${selectedTheme.colors.messageBackground}; | |||||
| border-radius: ${(props) => | |||||
| props.ismymessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"}; | |||||
| padding: 9px; | |||||
| padding-bottom: 31px; | |||||
| position: relative; | |||||
| min-height: 65px; | |||||
| margin: 0 18px; | |||||
| min-width: 110px; | |||||
| @media (max-width: 600px) { | |||||
| width: 100%; | |||||
| } | |||||
| `; | |||||
| export const MessageText = styled(Typography)` | |||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| font-size: 16px; | |||||
| line-height: 22px; | |||||
| color: ${selectedTheme.colors.messageText}; | |||||
| `; | |||||
| export const MessageDate = styled(Typography)` | |||||
| color: ${(props) => | |||||
| props.ismymessage | |||||
| ? selectedTheme.colors.messageMyDate | |||||
| : selectedTheme.colors.messageDate}; | |||||
| font-size: 12px; | |||||
| font-style: italic; | |||||
| position: absolute; | |||||
| bottom: 9px; | |||||
| left: 9px; | |||||
| `; | |||||
| export const InfoIcon = styled(Info)``; |
| import React from "react"; | |||||
| import PropTypes from "prop-types"; | |||||
| import { | |||||
| RequestExchangeMessageButton, | |||||
| RequestExchangeMessageButtonsContainer, | |||||
| RequestExchangeMessageContainer, | |||||
| RequestExchangeMessageText, | |||||
| } from "./RequestExchangeMessage.styled"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import { acceptExchangeSocket } from "../../../../socket/socket"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | |||||
| import { selectExchange, selectRequester } from "../../../../store/selectors/exchangeSelector"; | |||||
| import { acceptExchange, setRequester } from "../../../../store/actions/exchange/exchangeActions"; | |||||
| import { addNewMessage } from "../../../../store/actions/chat/chatActions"; | |||||
| import { convertLocalDateToUTCDate } from "../../../../util/helpers/dateHelpers"; | |||||
| import requesterStatus from "../../../../constants/requesterStatus"; | |||||
| const RequestExchangeMessage = (props) => { | |||||
| const { t } = useTranslation(); | |||||
| const dispatch = useDispatch(); | |||||
| const exchange = useSelector(selectExchange); | |||||
| const requester = useSelector(selectRequester); | |||||
| console.log(props); | |||||
| const handleAcceptExchange = () => { | |||||
| acceptExchangeSocket( | |||||
| props.chatId, | |||||
| props.userId, | |||||
| props.interlucatorUserId, | |||||
| () => { | |||||
| dispatch( | |||||
| acceptExchange({ | |||||
| exchangeId: exchange._id, | |||||
| }) | |||||
| ); | |||||
| dispatch( | |||||
| addNewMessage({ | |||||
| _id: props.chatId, | |||||
| message: { | |||||
| userId: props.userId, | |||||
| text: "", | |||||
| isAcceptRequest: true, | |||||
| _created: convertLocalDateToUTCDate(new Date()), | |||||
| }, | |||||
| }) | |||||
| ); | |||||
| if (requester === requesterStatus.NOONE) { | |||||
| dispatch(setRequester(requesterStatus.ME)); | |||||
| } | |||||
| } | |||||
| ); | |||||
| }; | |||||
| return ( | |||||
| <RequestExchangeMessageContainer> | |||||
| <RequestExchangeMessageText> | |||||
| {t("messages.requestReceived")} | |||||
| </RequestExchangeMessageText> | |||||
| <RequestExchangeMessageButtonsContainer> | |||||
| {!props.haveIAccepted && ( | |||||
| <RequestExchangeMessageButton variant="outlined" white> | |||||
| {t("messages.declineRequest")} | |||||
| </RequestExchangeMessageButton> | |||||
| )} | |||||
| <RequestExchangeMessageButton | |||||
| variant="contained" | |||||
| onClick={handleAcceptExchange} | |||||
| disabled={props.haveIAccepted} | |||||
| > | |||||
| {props.haveIAccepted ? t("messages.acceptedRequest") : t("messages.acceptRequest")} | |||||
| </RequestExchangeMessageButton> | |||||
| </RequestExchangeMessageButtonsContainer> | |||||
| </RequestExchangeMessageContainer> | |||||
| ); | |||||
| }; | |||||
| RequestExchangeMessage.propTypes = { | |||||
| children: PropTypes.node, | |||||
| chatId: PropTypes.string, | |||||
| userId: PropTypes.string, | |||||
| interlucatorUserId: PropTypes.string, | |||||
| haveIAccepted: PropTypes.any, | |||||
| }; | |||||
| export default RequestExchangeMessage; |
| import { Box, Typography } from "@mui/material"; | |||||
| import styled from "styled-components"; | |||||
| import selectedTheme from "../../../../themes"; | |||||
| import { PrimaryButton } from "../../../Buttons/PrimaryButton/PrimaryButton"; | |||||
| export const RequestExchangeMessageContainer = styled(Box)` | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 27px; | |||||
| width: 314px; | |||||
| margin-bottom: 10px; | |||||
| `; | |||||
| export const RequestExchangeMessageText = styled(Typography)` | |||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| font-weight: 600; | |||||
| font-size: 14px; | |||||
| line-height: 19px; | |||||
| text-align: center; | |||||
| `; | |||||
| export const RequestExchangeMessageButtonsContainer = styled(Box)` | |||||
| display: flex; | |||||
| flex-direction: row; | |||||
| justify-content: space-between; | |||||
| gap: 18px; | |||||
| `; | |||||
| export const RequestExchangeMessageButton = styled(PrimaryButton)` | |||||
| flex: 1; | |||||
| height: 45px; | |||||
| background: ${(props) => props.white && "white"}; | |||||
| & button { | |||||
| width: 100%; | |||||
| font-weight: 600; | |||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| letter-spacing: 1.5px; | |||||
| } | |||||
| `; |
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | ||||
| import { CHAT_SCOPE } from "../../store/actions/chat/chatActionConstants"; | import { CHAT_SCOPE } from "../../store/actions/chat/chatActionConstants"; | ||||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | import { selectUserId } from "../../store/selectors/loginSelectors"; | ||||
| import { addMesageListener, removeMessageListener } from "../../socket/socket"; | |||||
| import { | |||||
| acceptExchangeSocket, | |||||
| addMesageListener, | |||||
| removeMessageListener, | |||||
| } from "../../socket/socket"; | |||||
| import { makeErrorToastMessage } from "../../store/utils/makeToastMessage"; | import { makeErrorToastMessage } from "../../store/utils/makeToastMessage"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { | |||||
| selectExchange, | |||||
| selectRequester, | |||||
| } from "../../store/selectors/exchangeSelector"; | |||||
| import { | |||||
| acceptExchange, | |||||
| setRequester, | |||||
| } from "../../store/actions/exchange/exchangeActions"; | |||||
| import { convertLocalDateToUTCDate } from "../../util/helpers/dateHelpers"; | |||||
| import requesterStatus from "../../constants/requesterStatus"; | |||||
| import exchangeStatus from "../../constants/exchangeStatus"; | |||||
| const DirectChat = () => { | const DirectChat = () => { | ||||
| const chat = useSelector(selectSelectedChat); | const chat = useSelector(selectSelectedChat); | ||||
| const location = useLocation(); | const location = useLocation(); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const exchange = useSelector(selectExchange); | |||||
| const requester = useSelector(selectRequester); | |||||
| const userId = useSelector(selectUserId); | const userId = useSelector(selectUserId); | ||||
| const isLoadingDirectChat = useSelector( | const isLoadingDirectChat = useSelector( | ||||
| return chat; | return chat; | ||||
| }, [chat, location.state]); | }, [chat, location.state]); | ||||
| const amIBuyer = useMemo( | |||||
| () => exchange.buyer?.userId === userId, | |||||
| [exchange, userId] | |||||
| ); | |||||
| const exchangeState = useMemo(() => { | |||||
| if (exchange?.buyer) { | |||||
| let haveIAccepted = amIBuyer | |||||
| ? exchange.buyer?.accepted | |||||
| : exchange.seller?.accepted; | |||||
| let haveInterlucatorAccepted = amIBuyer | |||||
| ? exchange.seller?.accepted | |||||
| : exchange.buyer?.accepted; | |||||
| let haveIReviewed = amIBuyer | |||||
| ? exchange.buyer.givenReview | |||||
| : exchange.seller.givenReview; | |||||
| if (haveIAccepted) { | |||||
| if (haveInterlucatorAccepted) { | |||||
| if (haveIReviewed) { | |||||
| return exchangeStatus.REVIEWED; | |||||
| } else { | |||||
| return exchangeStatus.ACCEPTED; | |||||
| } | |||||
| } else { | |||||
| return exchangeStatus.I_OFFERED; | |||||
| } | |||||
| } else { | |||||
| if (haveInterlucatorAccepted) { | |||||
| return exchangeStatus.I_AM_OFFERED; | |||||
| } else { | |||||
| return exchangeStatus.INITIAL; | |||||
| } | |||||
| } | |||||
| } | |||||
| return exchangeStatus.INITIAL; | |||||
| }, [exchange, amIBuyer]); | |||||
| const interlocutorObject = useMemo(() => { | const interlocutorObject = useMemo(() => { | ||||
| if (location?.state?.offerId) { | if (location?.state?.offerId) { | ||||
| return { | return { | ||||
| message: data.message, | message: data.message, | ||||
| }) | }) | ||||
| ); | ); | ||||
| if ( | |||||
| data.message?.isAcceptRequest && | |||||
| requester === requesterStatus.NOONE | |||||
| ) { | |||||
| dispatch(setRequester(requesterStatus.INTERLUCATOR)); | |||||
| } | |||||
| } else { | } else { | ||||
| dispatch(fetchChats()); | dispatch(fetchChats()); | ||||
| } | } | ||||
| dispatch(fetchOneChat(routeMatch.params.idChat)); | dispatch(fetchOneChat(routeMatch.params.idChat)); | ||||
| } | } | ||||
| }; | }; | ||||
| const handleAcceptExchange = () => { | |||||
| acceptExchangeSocket( | |||||
| chat?.chat?._id, | |||||
| userId, | |||||
| chat?.interlocutor?._id, | |||||
| () => { | |||||
| dispatch( | |||||
| acceptExchange({ | |||||
| exchangeId: exchange._id, | |||||
| }) | |||||
| ); | |||||
| dispatch( | |||||
| addNewMessage({ | |||||
| _id: chat?.chat?._id, | |||||
| message: { | |||||
| userId, | |||||
| isAcceptRequest: true, | |||||
| text: "", | |||||
| _created: convertLocalDateToUTCDate(new Date()), | |||||
| }, | |||||
| }) | |||||
| ); | |||||
| if (requester === requesterStatus.NOONE) { | |||||
| dispatch(setRequester(requesterStatus.ME)); | |||||
| } | |||||
| } | |||||
| ); | |||||
| }; | |||||
| return ( | return ( | ||||
| <DirectChatContainer> | <DirectChatContainer> | ||||
| {isLoadingDirectChat || isLoadingDirectChat === undefined ? ( | {isLoadingDirectChat || isLoadingDirectChat === undefined ? ( | ||||
| <> | <> | ||||
| <DirectChatHeaderTitle /> | <DirectChatHeaderTitle /> | ||||
| <DirectChatHeader | <DirectChatHeader | ||||
| exchangeState={exchangeState} | |||||
| offer={offerObject} | offer={offerObject} | ||||
| interlocutor={interlocutorObject} | interlocutor={interlocutorObject} | ||||
| acceptExchange={handleAcceptExchange} | |||||
| /> | /> | ||||
| </> | </> | ||||
| )} | )} | ||||
| <DirectChatContent | <DirectChatContent | ||||
| chat={chatObject} | chat={chatObject} | ||||
| exchangeState={exchangeState} | |||||
| interlucator={interlocutorObject} | interlucator={interlocutorObject} | ||||
| refreshChat={refreshChat} | refreshChat={refreshChat} | ||||
| /> | /> |
| import SkeletonDirectChatContent from "./SkeletonDirectChatContent/SkeletonDirectChatContent"; | import SkeletonDirectChatContent from "./SkeletonDirectChatContent/SkeletonDirectChatContent"; | ||||
| import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors"; | import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors"; | ||||
| import { CHAT_SCOPE } from "../../../store/actions/chat/chatActionConstants"; | import { CHAT_SCOPE } from "../../../store/actions/chat/chatActionConstants"; | ||||
| import RequestExchangeCard from "../../Cards/RequestExchangeCard/RequestExchangeCard"; | |||||
| import { selectRequester } from "../../../store/selectors/exchangeSelector"; | |||||
| import requesterStatus from "../../../constants/requesterStatus"; | |||||
| const DirectChatContent = (props) => { | const DirectChatContent = (props) => { | ||||
| const userId = useSelector(selectUserId); | const userId = useSelector(selectUserId); | ||||
| const myProfileImage = useSelector(selectMineProfilePicture); | const myProfileImage = useSelector(selectMineProfilePicture); | ||||
| const messagesRef = useRef(null); | const messagesRef = useRef(null); | ||||
| const requester = useSelector(selectRequester); | |||||
| const interlucatorProfileImage = props?.interlucator?.image; | const interlucatorProfileImage = props?.interlucator?.image; | ||||
| const isLoadingChatContent = useSelector( | const isLoadingChatContent = useSelector( | ||||
| selectIsLoadingByActionType(CHAT_SCOPE) | selectIsLoadingByActionType(CHAT_SCOPE) | ||||
| <SkeletonDirectChatContent /> | <SkeletonDirectChatContent /> | ||||
| ) : ( | ) : ( | ||||
| <DirectChatContentContainer> | <DirectChatContentContainer> | ||||
| <DirectChatContentHeader interlucator={props?.interlucator} /> | |||||
| <DirectChatContentHeader | |||||
| interlucator={props?.interlucator} | |||||
| exchangeState={props.exchangeState} | |||||
| /> | |||||
| <MessagesList ref={messagesRef}> | <MessagesList ref={messagesRef}> | ||||
| {messages?.map((item) => { | {messages?.map((item) => { | ||||
| const isMyMessage = userId === item.userId; | const isMyMessage = userId === item.userId; | ||||
| const image = isMyMessage | const image = isMyMessage | ||||
| ? myProfileImage | ? myProfileImage | ||||
| : interlucatorProfileImage; | : interlucatorProfileImage; | ||||
| if (requester === requesterStatus.INTERLUCATOR && isMyMessage) | |||||
| return; | |||||
| return ( | return ( | ||||
| <MessageContainer key={item?._id || item?._created}> | <MessageContainer key={item?._id || item?._created}> | ||||
| <MessageCard | |||||
| message={item} | |||||
| image={image} | |||||
| isMyMessage={isMyMessage} | |||||
| /> | |||||
| {item?.isAcceptRequest ? ( | |||||
| <RequestExchangeCard | |||||
| isMyMessage={isMyMessage} | |||||
| message={item} | |||||
| chatId={props?.chat?.chat?._id} | |||||
| /> | |||||
| ) : ( | |||||
| <MessageCard | |||||
| message={item} | |||||
| image={image} | |||||
| isMyMessage={isMyMessage} | |||||
| /> | |||||
| )} | |||||
| </MessageContainer> | </MessageContainer> | ||||
| ); | ); | ||||
| })} | })} | ||||
| chat: PropTypes.any, | chat: PropTypes.any, | ||||
| interlucator: PropTypes.any, | interlucator: PropTypes.any, | ||||
| refreshChat: PropTypes.func, | refreshChat: PropTypes.func, | ||||
| exchangeState: PropTypes.any, | |||||
| }; | }; | ||||
| export default DirectChatContent; | export default DirectChatContent; |
| import { | import { | ||||
| DirectChatContentHeaderContainer, | DirectChatContentHeaderContainer, | ||||
| DirectChatContentHeaderFlexContainer, | DirectChatContentHeaderFlexContainer, | ||||
| DirectChatHeaderStatusContainer, | |||||
| DirectChatHeaderStatusText, | |||||
| PhoneIcon, | PhoneIcon, | ||||
| PhoneIconContainer, | PhoneIconContainer, | ||||
| ProfileDetails, | ProfileDetails, | ||||
| import { PROFILE_PAGE } from "../../../../constants/pages"; | import { PROFILE_PAGE } from "../../../../constants/pages"; | ||||
| import { selectAmIBlocked } from "../../../../store/selectors/profileSelectors"; | import { selectAmIBlocked } from "../../../../store/selectors/profileSelectors"; | ||||
| import { useSelector } from "react-redux"; | import { useSelector } from "react-redux"; | ||||
| import { useTranslation } from "react-i18next"; | |||||
| import exchangeStatus from "../../../../constants/exchangeStatus"; | |||||
| const DirectChatContentHeader = (props) => { | const DirectChatContentHeader = (props) => { | ||||
| const { t } = useTranslation(); | |||||
| const [showPhonePopover, setShowPhonePopover] = useState(false); | const [showPhonePopover, setShowPhonePopover] = useState(false); | ||||
| const [phonePopoverAnchorEl, setPhonePopoverAnchorEl] = useState(null); | const [phonePopoverAnchorEl, setPhonePopoverAnchorEl] = useState(null); | ||||
| const { isMobile } = useIsMobile(); | const { isMobile } = useIsMobile(); | ||||
| ); | ); | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <DirectChatContentHeaderContainer> | |||||
| <DirectChatContentHeaderFlexContainer> | |||||
| <ProfileImage | |||||
| onClick={routeToUser} | |||||
| src={getImageUrl( | |||||
| props?.interlucator?.image, | |||||
| variants.chatHeader, | |||||
| isMobile | |||||
| )} | |||||
| <> | |||||
| <DirectChatContentHeaderContainer> | |||||
| <DirectChatContentHeaderFlexContainer> | |||||
| <ProfileImage | |||||
| onClick={routeToUser} | |||||
| src={getImageUrl( | |||||
| props?.interlucator?.image, | |||||
| variants.chatHeader, | |||||
| isMobile | |||||
| )} | |||||
| /> | |||||
| <ProfileDetails> | |||||
| <ProfileName onClick={routeToUser}> | |||||
| {props?.interlucator?.name} | |||||
| </ProfileName> | |||||
| <ProfileLocation> | |||||
| <ProfileLocationIcon /> | |||||
| <ProfileLocationText> | |||||
| {props?.interlucator?.location} | |||||
| </ProfileLocationText> | |||||
| </ProfileLocation> | |||||
| </ProfileDetails> | |||||
| </DirectChatContentHeaderFlexContainer> | |||||
| <DirectChatContentHeaderFlexContainer> | |||||
| <PhoneIconContainer | |||||
| disabled={ | |||||
| mineProfileBlocked || | |||||
| props?.interlucator?._blocked || | |||||
| !props.interlucator?.telephone | |||||
| } | |||||
| onClick={togglePhonePopover} | |||||
| > | |||||
| <PhoneIcon /> | |||||
| </PhoneIconContainer> | |||||
| </DirectChatContentHeaderFlexContainer> | |||||
| <PopoverComponent | |||||
| anchorEl={phonePopoverAnchorEl} | |||||
| open={showPhonePopover} | |||||
| anchorRight | |||||
| onClose={togglePhonePopover} | |||||
| content={<PhonePopover phoneNumber={props.interlucator?.telephone} />} | |||||
| /> | /> | ||||
| <ProfileDetails> | |||||
| <ProfileName onClick={routeToUser}> | |||||
| {props?.interlucator?.name} | |||||
| </ProfileName> | |||||
| <ProfileLocation> | |||||
| <ProfileLocationIcon /> | |||||
| <ProfileLocationText> | |||||
| {props?.interlucator?.location} | |||||
| </ProfileLocationText> | |||||
| </ProfileLocation> | |||||
| </ProfileDetails> | |||||
| </DirectChatContentHeaderFlexContainer> | |||||
| <DirectChatContentHeaderFlexContainer> | |||||
| <PhoneIconContainer | |||||
| disabled={ | |||||
| mineProfileBlocked || | |||||
| props?.interlucator?._blocked || | |||||
| !props.interlucator?.telephone | |||||
| } | |||||
| onClick={togglePhonePopover} | |||||
| > | |||||
| <PhoneIcon /> | |||||
| </PhoneIconContainer> | |||||
| </DirectChatContentHeaderFlexContainer> | |||||
| <PopoverComponent | |||||
| anchorEl={phonePopoverAnchorEl} | |||||
| open={showPhonePopover} | |||||
| anchorRight | |||||
| onClose={togglePhonePopover} | |||||
| content={<PhonePopover phoneNumber={props.interlucator?.telephone} />} | |||||
| /> | |||||
| </DirectChatContentHeaderContainer> | |||||
| </DirectChatContentHeaderContainer> | |||||
| {(props.exchangeState === exchangeStatus.I_OFFERED || | |||||
| props.exchangeState === exchangeStatus.ACCEPTED || | |||||
| props.exchangeState === exchangeStatus.REVIEWED) && ( | |||||
| <DirectChatHeaderStatusContainer> | |||||
| <DirectChatHeaderStatusText> | |||||
| {props.exchangeState === exchangeStatus.I_OFFERED | |||||
| ? t("messages.requestSentLong") | |||||
| : t("messages.requestSuccessfulLong")} | |||||
| </DirectChatHeaderStatusText> | |||||
| </DirectChatHeaderStatusContainer> | |||||
| )} | |||||
| </> | |||||
| ); | ); | ||||
| }; | }; | ||||
| DirectChatContentHeader.propTypes = { | DirectChatContentHeader.propTypes = { | ||||
| children: PropTypes.node, | children: PropTypes.node, | ||||
| interlucator: PropTypes.any, | interlucator: PropTypes.any, | ||||
| exchangeState: PropTypes.bool, | |||||
| }; | }; | ||||
| export default DirectChatContentHeader; | export default DirectChatContentHeader; |
| import { Box } 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 { ReactComponent as Location } from "../../../../assets/images/svg/location.svg"; | import { ReactComponent as Location } from "../../../../assets/images/svg/location.svg"; | ||||
| height: 32px; | height: 32px; | ||||
| } | } | ||||
| `; | `; | ||||
| export const DirectChatHeaderStatusContainer = styled(Box)` | |||||
| background-color: ${selectedTheme.colors.primaryPurple}; | |||||
| height: 39px; | |||||
| width: 100%; | |||||
| padding: 9px 36px; | |||||
| `; | |||||
| export const DirectChatHeaderStatusText = styled(Typography)` | |||||
| font-family: ${selectedTheme.fonts.textFont}; | |||||
| font-style: italic; | |||||
| font-size: 16px; | |||||
| line-height: 21px; | |||||
| color: white; | |||||
| `; |
| return false; | return false; | ||||
| }, [exchange, userId]); | }, [exchange, userId]); | ||||
| const showReviewModal = (event) => { | |||||
| event.stopPropagation(); | |||||
| const showReviewModal = () => { | |||||
| dispatch( | dispatch( | ||||
| toggleCreateReviewModal({ | toggleCreateReviewModal({ | ||||
| offer: props.offer, | offer: props.offer, | ||||
| const refetchExchange = () => { | const refetchExchange = () => { | ||||
| dispatch(fetchExchange(chat.chat.exchangeId)); | dispatch(fetchExchange(chat.chat.exchangeId)); | ||||
| }; | }; | ||||
| const acceptExchange = () => { | |||||
| props.acceptExchange(); | |||||
| }; | |||||
| return ( | return ( | ||||
| <DirectChatHeaderContainer> | <DirectChatHeaderContainer> | ||||
| <OfferCard | <OfferCard | ||||
| aboveChat | aboveChat | ||||
| disabledReviews={props.interlocutor?._blocked || isDisabledReviews} | disabledReviews={props.interlocutor?._blocked || isDisabledReviews} | ||||
| makeReview={showReviewModal} | makeReview={showReviewModal} | ||||
| acceptExchange={acceptExchange} | |||||
| exchangeState={props?.exchangeState} | |||||
| dontShowViews | dontShowViews | ||||
| disabledCheckButton={ | disabledCheckButton={ | ||||
| props.interlocutor?._blocked || props?.offer?._deleted | props.interlocutor?._blocked || props?.offer?._deleted | ||||
| children: PropTypes.node, | children: PropTypes.node, | ||||
| offer: PropTypes.any, | offer: PropTypes.any, | ||||
| interlocutor: PropTypes.any, | interlocutor: PropTypes.any, | ||||
| acceptExchange: PropTypes.func, | |||||
| exchangeState: PropTypes.any, | |||||
| }; | }; | ||||
| export default DirectChatHeader; | export default DirectChatHeader; |
| selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE) | selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE) | ||||
| ); | ); | ||||
| const chats = useSelector(selectLatestChats); | const chats = useSelector(selectLatestChats); | ||||
| const sortRef = useRef(null); | |||||
| const profileOffers = useSelector(selectProfileOffers); | const profileOffers = useSelector(selectProfileOffers); | ||||
| const { isMobile } = useIsMobile(); | const { isMobile } = useIsMobile(); | ||||
| const userId = useSelector(selectUserId); | const userId = useSelector(selectUserId); | ||||
| }, [searchValue, sortOption, paging.currentPage]); | }, [searchValue, sortOption, paging.currentPage]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if ( | |||||
| isLoadingMineOffers === false && | |||||
| searchRef.current && | |||||
| searchRef.current?.searchValue !== searchValue | |||||
| ) { | |||||
| searchRef.current.changeSearchValue(searchValue); | |||||
| if (isLoadingMineOffers === false) { | |||||
| if (searchRef.current && searchRef.current?.searchValue !== searchValue) | |||||
| searchRef.current.changeSearchValue(searchValue); | |||||
| if (sortRef.current && sortRef.current?.sortOption !== sortOption) | |||||
| sortRef.current.setSortOption(sortOption); | |||||
| } | } | ||||
| }, [isLoadingMineOffers]); | }, [isLoadingMineOffers]); | ||||
| ) : ( | ) : ( | ||||
| <OffersContainer> | <OffersContainer> | ||||
| <SelectSortField | <SelectSortField | ||||
| ref={sortRef} | |||||
| offersToShow={profileOffers} | offersToShow={profileOffers} | ||||
| setSortOption={handleChangeSortOption} | setSortOption={handleChangeSortOption} | ||||
| /> | /> |
| } from "./SelectSortField.styled"; | } from "./SelectSortField.styled"; | ||||
| import { sortEnum } from "../../../../enums/sortEnum"; | import { sortEnum } from "../../../../enums/sortEnum"; | ||||
| import { useState } from "react"; | import { useState } from "react"; | ||||
| import { forwardRef } from "react"; | |||||
| import { useImperativeHandle } from "react"; | |||||
| const SelectSortField = (props) => { | |||||
| const SelectSortField = forwardRef((props, ref) => { | |||||
| const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | const [sortOption, setSortOption] = useState(sortEnum.INITIAL); | ||||
| useImperativeHandle(ref, () => ({ | |||||
| setSortOption, | |||||
| sortOption | |||||
| })) | |||||
| const handleChangeSelect = (event) => { | const handleChangeSelect = (event) => { | ||||
| let chosenOption; | let chosenOption; | ||||
| for (const sortOption in sortEnum) { | for (const sortOption in sortEnum) { | ||||
| })} | })} | ||||
| </HeaderSelect> | </HeaderSelect> | ||||
| ); | ); | ||||
| }; | |||||
| }); | |||||
| SelectSortField.displayName = "SelectSortField"; | |||||
| SelectSortField.propTypes = { | SelectSortField.propTypes = { | ||||
| offersToShow: PropTypes.array, | offersToShow: PropTypes.array, |
| export default { | |||||
| INITIAL: 0, | |||||
| I_OFFERED: 1, | |||||
| I_AM_OFFERED: 2, | |||||
| ACCEPTED: 3, // NOT REVIEWED | |||||
| REVIEWED: 4, | |||||
| } |
| export default { | |||||
| NOONE: 0, | |||||
| ME: 1, | |||||
| INTERLUCATOR: 2 | |||||
| } |
| noMessages: "Poruke nisu pronađene.", | noMessages: "Poruke nisu pronađene.", | ||||
| noMessagesSecond: "Nažalost, nemate ni jednu poruku.", | noMessagesSecond: "Nažalost, nemate ni jednu poruku.", | ||||
| tooltip: "Pošalji poruku", | tooltip: "Pošalji poruku", | ||||
| requestSent: "Uspešno ste ponudili trampu kompaniji.", | |||||
| requestAccepted: "Kompanija je prihvatila trampu sa Vama.", | |||||
| requestReceived: "Da li želite da prihvatite trampu sa nama za gorenavedeni proizvod?", | |||||
| acceptRequest: "Prihvati", | |||||
| acceptedRequest: "Prihvaćeno", | |||||
| declineRequest: "Odbij", | |||||
| requestSuccessfulLong: "Uspešno ste ostvarili trampu sa ovom kompanijom.", | |||||
| requestSentLong: "Ponudili ste trampu kompaniji. Čeka se odgovor..." | |||||
| }, | }, | ||||
| editProfile: { | editProfile: { | ||||
| website: "Web Sajt", | website: "Web Sajt", |
| import { transitionOnLoadFromRight } from "../../components/Styles/globalStyleComponents"; | import { transitionOnLoadFromRight } from "../../components/Styles/globalStyleComponents"; | ||||
| export const ChatMessagesPageContainer = styled(Container)` | export const ChatMessagesPageContainer = styled(Container)` | ||||
| max-width: none; | |||||
| @media (max-width: 600px) { | @media (max-width: 600px) { | ||||
| animation: 0.2s ease 0s 1 ${transitionOnLoadFromRight}; | animation: 0.2s ease 0s 1 ${transitionOnLoadFromRight}; | ||||
| } | } |
| exchange: { | exchange: { | ||||
| getExchange: "exchanges", | getExchange: "exchanges", | ||||
| validateExchange: "exchanges", | validateExchange: "exchanges", | ||||
| acceptExchange: "users/{userId}/exchanges/{exchangeId}/accept" | |||||
| }, | }, | ||||
| reviews: { | reviews: { | ||||
| getUserReviews: "/users/{userId}/reviews", | getUserReviews: "/users/{userId}/reviews", |
| import { getRequest, patchRequest } from "." | |||||
| import apiEndpoints from "./apiEndpoints" | |||||
| import { getRequest, patchRequest, replaceInUrl } from "."; | |||||
| import apiEndpoints from "./apiEndpoints"; | |||||
| export const attemptFetchExchange = (payload) => { | export const attemptFetchExchange = (payload) => { | ||||
| return getRequest(apiEndpoints.exchange.getExchange + `/${payload}`); | |||||
| } | |||||
| return getRequest(apiEndpoints.exchange.getExchange + `/${payload}`); | |||||
| }; | |||||
| export const attemptValidateExchange = (payload) => { | export const attemptValidateExchange = (payload) => { | ||||
| return patchRequest(apiEndpoints.exchange.validateExchange + `/${payload}`); | |||||
| } | |||||
| return patchRequest(apiEndpoints.exchange.validateExchange + `/${payload}`); | |||||
| }; | |||||
| export const attemptAcceptExchange = (userId, exchangeId) => { | |||||
| return patchRequest( | |||||
| replaceInUrl(apiEndpoints.exchange.acceptExchange, { | |||||
| userId, | |||||
| exchangeId, | |||||
| }) | |||||
| ); | |||||
| }; |
| // baseURL: "http://192.168.88.175:3005/", | // baseURL: "http://192.168.88.175:3005/", | ||||
| // baseURL: "http://192.168.88.143:3001/", // DULE | // baseURL: "http://192.168.88.143:3001/", // DULE | ||||
| // baseURL: "https://trampa-api.dilig.net/", | // baseURL: "https://trampa-api.dilig.net/", | ||||
| baseURL: "https://trampa-api-test.dilig.net/", | |||||
| baseURL: "https://trampa-qa-api.dilig.net/", | |||||
| // baseURL: "http://localhost:3001/", | // baseURL: "http://localhost:3001/", | ||||
| // baseURL: process.env.REACT_APP_BASE_API_URL, | // baseURL: process.env.REACT_APP_BASE_API_URL, | ||||
| headers: { | headers: { |
| import io from "socket.io-client"; | import io from "socket.io-client"; | ||||
| export const socket = io("https://trampa-api-test.dilig.net/", { | |||||
| export const socket = io("https://trampa-qa-api.dilig.net/", { | |||||
| // export const socket = io("http://localhost:3001/", { | // export const socket = io("http://localhost:3001/", { | ||||
| // export const socket = io(process.env.REACT_APP_BASE_API_URL, { | // export const socket = io(process.env.REACT_APP_BASE_API_URL, { | ||||
| transports: ["websocket"], | transports: ["websocket"], | ||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| export const acceptExchangeSocket = (chatId, userId, receiverUserId, callbackFn) => { | |||||
| socket.emit("private_message", { | |||||
| chatId, | |||||
| receiverUserId, | |||||
| message: { | |||||
| userId, | |||||
| isAcceptRequest: true, | |||||
| text: "" | |||||
| }, | |||||
| }); | |||||
| callbackFn(); | |||||
| }; | |||||
| export const addMesageListener = (listener) => { | export const addMesageListener = (listener) => { | ||||
| socket.on("private_message", (data) => | socket.on("private_message", (data) => |
| export const EXCHANGE_VALIDATE_FETCH_ERROR = createErrorType( | export const EXCHANGE_VALIDATE_FETCH_ERROR = createErrorType( | ||||
| EXCHANGE_VALIDATE_SCOPE | EXCHANGE_VALIDATE_SCOPE | ||||
| ); | ); | ||||
| const EXCHANGE_ACCEPT_SCOPE = "EXCHANGE_ACCEPT_SCOPE"; | |||||
| export const EXCHANGE_ACCEPT_FETCH = createFetchType(EXCHANGE_ACCEPT_SCOPE); | |||||
| export const EXCHANGE_ACCEPT_FETCH_SUCCESS = createSuccessType( | |||||
| EXCHANGE_ACCEPT_SCOPE | |||||
| ); | |||||
| export const EXCHANGE_ACCEPT_FETCH_ERROR = createErrorType( | |||||
| EXCHANGE_ACCEPT_SCOPE | |||||
| ); | |||||
| export const EXCHANGE_SET = createSetType("EXCHANGE_SET"); | export const EXCHANGE_SET = createSetType("EXCHANGE_SET"); | ||||
| export const REQUESTER_SET = createSetType("REQUESTER_SET"); |
| import { EXCHANGE_FETCH, EXCHANGE_FETCH_ERROR, EXCHANGE_FETCH_SUCCESS, EXCHANGE_SET, EXCHANGE_VALIDATE_FETCH, EXCHANGE_VALIDATE_FETCH_ERROR, EXCHANGE_VALIDATE_FETCH_SUCCESS } from "./exchangeActionConstants"; | |||||
| export const fetchExchange = (payload) => ({ | |||||
| type: EXCHANGE_FETCH, | |||||
| payload, | |||||
| }) | |||||
| import { | |||||
| EXCHANGE_ACCEPT_FETCH, | |||||
| EXCHANGE_ACCEPT_FETCH_ERROR, | |||||
| EXCHANGE_ACCEPT_FETCH_SUCCESS, | |||||
| EXCHANGE_FETCH, | |||||
| EXCHANGE_FETCH_ERROR, | |||||
| EXCHANGE_FETCH_SUCCESS, | |||||
| EXCHANGE_SET, | |||||
| EXCHANGE_VALIDATE_FETCH, | |||||
| EXCHANGE_VALIDATE_FETCH_ERROR, | |||||
| EXCHANGE_VALIDATE_FETCH_SUCCESS, | |||||
| REQUESTER_SET, | |||||
| } from "./exchangeActionConstants"; | |||||
| export const setExchange = (payload) => ({ | export const setExchange = (payload) => ({ | ||||
| type: EXCHANGE_SET, | |||||
| payload, | |||||
| }) | |||||
| export const validateExchange = (payload) => ({ | |||||
| type: EXCHANGE_VALIDATE_FETCH, | |||||
| payload, | |||||
| }) | |||||
| type: EXCHANGE_SET, | |||||
| payload, | |||||
| }); | |||||
| export const setRequester = (payload) => ({ | |||||
| type: REQUESTER_SET, | |||||
| payload, | |||||
| }); | |||||
| export const fetchExchange = (payload) => ({ | |||||
| type: EXCHANGE_FETCH, | |||||
| payload, | |||||
| }); | |||||
| export const fetchExchangeSuccess = () => ({ | export const fetchExchangeSuccess = () => ({ | ||||
| type: EXCHANGE_FETCH_SUCCESS | |||||
| }) | |||||
| type: EXCHANGE_FETCH_SUCCESS, | |||||
| }); | |||||
| export const fetchExchangeError = () => ({ | export const fetchExchangeError = () => ({ | ||||
| type: EXCHANGE_FETCH_ERROR | |||||
| }) | |||||
| type: EXCHANGE_FETCH_ERROR, | |||||
| }); | |||||
| export const validateExchange = (payload) => ({ | |||||
| type: EXCHANGE_VALIDATE_FETCH, | |||||
| payload, | |||||
| }); | |||||
| export const validateExchangeSuccess = () => ({ | export const validateExchangeSuccess = () => ({ | ||||
| type: EXCHANGE_VALIDATE_FETCH_SUCCESS | |||||
| }) | |||||
| type: EXCHANGE_VALIDATE_FETCH_SUCCESS, | |||||
| }); | |||||
| export const validateExchangeError = () => ({ | export const validateExchangeError = () => ({ | ||||
| type: EXCHANGE_VALIDATE_FETCH_ERROR | |||||
| }) | |||||
| type: EXCHANGE_VALIDATE_FETCH_ERROR, | |||||
| }); | |||||
| export const acceptExchange = (payload) => ({ | |||||
| type: EXCHANGE_ACCEPT_FETCH, | |||||
| payload, | |||||
| }); | |||||
| export const acceptExchangeSuccess = () => ({ | |||||
| type: EXCHANGE_ACCEPT_FETCH_SUCCESS, | |||||
| }); | |||||
| export const acceptExchangeError = () => ({ | |||||
| type: EXCHANGE_ACCEPT_FETCH_ERROR, | |||||
| }); |
| // const baseURL = "http://192.168.88.143:3001/"; // DULE | // const baseURL = "http://192.168.88.143:3001/"; // DULE | ||||
| // const baseURL = "http://192.168.88.175:3005/"; | // const baseURL = "http://192.168.88.175:3005/"; | ||||
| // const baseURL = "https://trampa-api.dilig.net/"; | // const baseURL = "https://trampa-api.dilig.net/"; | ||||
| const baseURL = "https://trampa-api-test.dilig.net/"; | |||||
| const baseURL = "https://trampa-qa-api.dilig.net/"; | |||||
| // const baseURL = "http://192.168.88.150:3001/"; // DJOLE | // const baseURL = "http://192.168.88.150:3001/"; // DJOLE | ||||
| // const baseURL = "http://localhost:3001/"; | // const baseURL = "http://localhost:3001/"; | ||||
| // const baseURL = process.env.REACT_APP_BASE_API_URL | // const baseURL = process.env.REACT_APP_BASE_API_URL |
| import { EXCHANGE_SET } from "../../actions/exchange/exchangeActionConstants" | |||||
| import requesterStatus from "../../../constants/requesterStatus" | |||||
| import { EXCHANGE_SET, REQUESTER_SET } from "../../actions/exchange/exchangeActionConstants" | |||||
| import createReducer from "../../utils/createReducer" | import createReducer from "../../utils/createReducer" | ||||
| const initialState = { | const initialState = { | ||||
| exchange: {} | |||||
| exchange: {}, | |||||
| requester: requesterStatus.NOONE, | |||||
| } | } | ||||
| export default createReducer( | export default createReducer( | ||||
| { | { | ||||
| [EXCHANGE_SET]: setExchange, | [EXCHANGE_SET]: setExchange, | ||||
| [REQUESTER_SET]: setRequester, | |||||
| }, | }, | ||||
| initialState | initialState | ||||
| ) | ) | ||||
| ...state, | ...state, | ||||
| exchange: action.payload | exchange: action.payload | ||||
| } | } | ||||
| } | |||||
| function setRequester(state, action) { | |||||
| return { | |||||
| ...state, | |||||
| requester: action.payload | |||||
| } | |||||
| } | } |
| SET_IS_APPLIED, | SET_IS_APPLIED, | ||||
| SET_LOCATIONS, | SET_LOCATIONS, | ||||
| SET_MANUAL_SEARCH_STRING, | SET_MANUAL_SEARCH_STRING, | ||||
| SET_QUERY_STRING, | |||||
| SET_SEARCH_STRING, | SET_SEARCH_STRING, | ||||
| SET_SORT_OPTION, | SET_SORT_OPTION, | ||||
| SET_SUBCATEGORY, | SET_SUBCATEGORY, | ||||
| [SET_IS_APPLIED]: setIsAppliedStatus, | [SET_IS_APPLIED]: setIsAppliedStatus, | ||||
| [SET_HEADER_STRING]: setHeaderString, | [SET_HEADER_STRING]: setHeaderString, | ||||
| [SET_SEARCH_STRING]: setSearchString, | [SET_SEARCH_STRING]: setSearchString, | ||||
| [SET_QUERY_STRING]: setQueryString, | |||||
| [SET_MANUAL_SEARCH_STRING]: setManualSearchString, | [SET_MANUAL_SEARCH_STRING]: setManualSearchString, | ||||
| }, | }, | ||||
| initialState | initialState | ||||
| }, | }, | ||||
| }; | }; | ||||
| } | } | ||||
| function setQueryString(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| filters: { | |||||
| ...state.filters, | |||||
| queryString: payload, | |||||
| }, | |||||
| }; | |||||
| } |
| startNewChatError, | startNewChatError, | ||||
| startNewChatSuccess, | startNewChatSuccess, | ||||
| } from "../actions/chat/chatActions"; | } from "../actions/chat/chatActions"; | ||||
| import { validateExchange } from "../actions/exchange/exchangeActions"; | |||||
| import { | |||||
| setRequester, | |||||
| validateExchange, | |||||
| } from "../actions/exchange/exchangeActions"; | |||||
| import { selectSelectedChat } from "../selectors/chatSelectors"; | import { selectSelectedChat } from "../selectors/chatSelectors"; | ||||
| import { selectExchange } from "../selectors/exchangeSelector"; | import { selectExchange } from "../selectors/exchangeSelector"; | ||||
| import { selectUserId } from "../selectors/loginSelectors"; | import { selectUserId } from "../selectors/loginSelectors"; | ||||
| import { KEY_PAGE, KEY_SIZE } from "../../constants/queryStringConstants"; | import { KEY_PAGE, KEY_SIZE } from "../../constants/queryStringConstants"; | ||||
| import { setQueryStringRedux } from "../actions/queryString/queryStringActions"; | import { setQueryStringRedux } from "../actions/queryString/queryStringActions"; | ||||
| import { selectQueryString } from "../selectors/queryStringSelectors"; | import { selectQueryString } from "../selectors/queryStringSelectors"; | ||||
| import requesterStatus from "../../constants/requesterStatus"; | |||||
| function* fetchChats({ payload }) { | function* fetchChats({ payload }) { | ||||
| try { | try { | ||||
| chatId: payload.payload, | chatId: payload.payload, | ||||
| userId, | userId, | ||||
| }); | }); | ||||
| let requester = requesterStatus.NOONE; | |||||
| chatData.data.chat.messages.forEach((item) => { | |||||
| if (requester !== requesterStatus.NOONE) return; | |||||
| if (item.isAcceptRequest) { | |||||
| if (item.userId === userId) requester = requesterStatus.ME; | |||||
| else requester = requesterStatus.INTERLUCATOR; | |||||
| } | |||||
| }); | |||||
| const chat = { | const chat = { | ||||
| chat: chatData.data.chat, | chat: chatData.data.chat, | ||||
| offer: { | offer: { | ||||
| }, | }, | ||||
| interlocutor: chatData.data.interlocutorData, | interlocutor: chatData.data.interlocutorData, | ||||
| }; | }; | ||||
| yield put(setRequester(requester)); | |||||
| yield put(setOneChat(chat)); | yield put(setOneChat(chat)); | ||||
| yield put(fetchOneChatSuccess()); | yield put(fetchOneChatSuccess()); | ||||
| } catch (e) { | } catch (e) { |
| import { all, takeLatest, call, put } from "@redux-saga/core/effects"; | |||||
| import { attemptFetchExchange, attemptValidateExchange } from "../../request/exchangeRequest"; | |||||
| import { EXCHANGE_FETCH, EXCHANGE_VALIDATE_FETCH } from "../actions/exchange/exchangeActionConstants"; | |||||
| import { fetchExchangeError, fetchExchangeSuccess, setExchange, validateExchangeError, validateExchangeSuccess } from "../actions/exchange/exchangeActions"; | |||||
| import { all, takeLatest, call, put, select } from "@redux-saga/core/effects"; | |||||
| import { | |||||
| attemptAcceptExchange, | |||||
| attemptFetchExchange, | |||||
| attemptValidateExchange, | |||||
| } from "../../request/exchangeRequest"; | |||||
| import { | |||||
| EXCHANGE_ACCEPT_FETCH, | |||||
| EXCHANGE_FETCH, | |||||
| EXCHANGE_VALIDATE_FETCH, | |||||
| } from "../actions/exchange/exchangeActionConstants"; | |||||
| import { | |||||
| acceptExchangeError, | |||||
| acceptExchangeSuccess, | |||||
| fetchExchangeError, | |||||
| fetchExchangeSuccess, | |||||
| setExchange, | |||||
| validateExchangeError, | |||||
| validateExchangeSuccess, | |||||
| } from "../actions/exchange/exchangeActions"; | |||||
| import { selectUserId } from "../selectors/loginSelectors"; | |||||
| function* fetchExchange(payload) { | function* fetchExchange(payload) { | ||||
| try { | |||||
| const data = yield call(attemptFetchExchange, payload.payload); | |||||
| yield put(setExchange(data.data)); | |||||
| yield put(fetchExchangeSuccess()); | |||||
| } | |||||
| catch (e) { | |||||
| yield put(fetchExchangeError()); | |||||
| console.dir(e); | |||||
| } | |||||
| try { | |||||
| const data = yield call(attemptFetchExchange, payload.payload); | |||||
| yield put(setExchange(data.data)); | |||||
| yield put(fetchExchangeSuccess()); | |||||
| } catch (e) { | |||||
| yield put(fetchExchangeError()); | |||||
| console.dir(e); | |||||
| } | |||||
| } | } | ||||
| function* validateExchange(payload) { | function* validateExchange(payload) { | ||||
| try { | |||||
| yield call(attemptValidateExchange, payload.payload); | |||||
| const data = yield call(attemptFetchExchange, payload.payload); | |||||
| yield put(setExchange(data.data)); | |||||
| yield put(validateExchangeSuccess()); | |||||
| } | |||||
| catch(e) { | |||||
| yield put(validateExchangeError()); | |||||
| console.dir(e); | |||||
| } | |||||
| try { | |||||
| yield call(attemptValidateExchange, payload.payload); | |||||
| const data = yield call(attemptFetchExchange, payload.payload); | |||||
| yield put(setExchange(data.data)); | |||||
| yield put(validateExchangeSuccess()); | |||||
| } catch (e) { | |||||
| yield put(validateExchangeError()); | |||||
| console.dir(e); | |||||
| } | |||||
| } | |||||
| function* acceptExchange({ payload }) { | |||||
| try { | |||||
| const userId = yield select(selectUserId); | |||||
| yield call(attemptAcceptExchange, userId, payload.exchangeId); | |||||
| const data = yield call(attemptFetchExchange, payload.exchangeId); | |||||
| yield put(setExchange(data.data)); | |||||
| yield put(acceptExchangeSuccess()); | |||||
| } catch (e) { | |||||
| yield put(acceptExchangeError()); | |||||
| console.dir(e); | |||||
| } | |||||
| } | } | ||||
| export default function* exchangeSaga() { | export default function* exchangeSaga() { | ||||
| yield all([ | |||||
| takeLatest(EXCHANGE_FETCH, fetchExchange), | |||||
| takeLatest(EXCHANGE_VALIDATE_FETCH, validateExchange) | |||||
| ]); | |||||
| } | |||||
| yield all([ | |||||
| takeLatest(EXCHANGE_FETCH, fetchExchange), | |||||
| takeLatest(EXCHANGE_VALIDATE_FETCH, validateExchange), | |||||
| takeLatest(EXCHANGE_ACCEPT_FETCH, acceptExchange), | |||||
| ]); | |||||
| } |
| KEY_SIZE, | KEY_SIZE, | ||||
| KEY_SORTBY, | KEY_SORTBY, | ||||
| } from "../../constants/queryStringConstants"; | } from "../../constants/queryStringConstants"; | ||||
| import { selectQueryString } from "../selectors/filtersSelectors"; | |||||
| import { setQueryString } from "../actions/filters/filtersActions"; | |||||
| function* fetchOffers(payload) { | function* fetchOffers(payload) { | ||||
| try { | try { | ||||
| function* fetchProfileOffers(payload) { | function* fetchProfileOffers(payload) { | ||||
| try { | try { | ||||
| console.log(payload.payload); | console.log(payload.payload); | ||||
| const userId = payload.payload.idProfile; | |||||
| let userId; | |||||
| if (typeof payload.payload === "object") { | |||||
| userId = payload.payload.idProfile; | |||||
| } else userId = payload.payload; | |||||
| if (!userId || userId?.length === 0) | if (!userId || userId?.length === 0) | ||||
| throw new Error("User id is not defined!"); | throw new Error("User id is not defined!"); | ||||
| const queryString = new URLSearchParams(); | |||||
| let queryString = new URLSearchParams(); | |||||
| queryString.set(KEY_SIZE, 10); | queryString.set(KEY_SIZE, 10); | ||||
| queryString.set(KEY_PAGE, payload.payload.page); | |||||
| if (payload.payload.searchValue?.length > 0) | |||||
| queryString.set(KEY_NAME, payload.payload.searchValue); | |||||
| queryString.set(KEY_SORTBY, payload.payload.sortOption.queryString); | |||||
| if (payload.payload[KEY_PAGE]) { | |||||
| queryString.set(KEY_PAGE, payload.payload.page); | |||||
| if (payload.payload.searchValue?.length > 0) | |||||
| queryString.set(KEY_NAME, payload.payload.searchValue); | |||||
| queryString.set(KEY_SORTBY, payload.payload.sortOption.queryString); | |||||
| yield put(setQueryString(queryString.toString())); | |||||
| } else { | |||||
| queryString = yield select(selectQueryString); | |||||
| } | |||||
| console.log(queryString.toString()); | |||||
| const data = yield call( | const data = yield call( | ||||
| attemptFetchProfileOffers, | attemptFetchProfileOffers, | ||||
| userId, | userId, |
| export const selectExchange = createSelector( | export const selectExchange = createSelector( | ||||
| exchangeSelector, | exchangeSelector, | ||||
| (state) => state.exchange | (state) => state.exchange | ||||
| ) | |||||
| export const selectRequester = createSelector( | |||||
| exchangeSelector, | |||||
| (state) => state.requester | |||||
| ) | ) |
| filtersSelector, | filtersSelector, | ||||
| (state) => state.filters.searchString | (state) => state.filters.searchString | ||||
| ); | ); | ||||
| export const selectQueryString = createSelector( | |||||
| filtersSelector, | |||||
| (state) => state.filters.queryString | |||||
| ); | |||||
| export const selectManualSearchString = createSelector( | export const selectManualSearchString = createSelector( | ||||
| filtersSelector, | filtersSelector, | ||||
| (state) => state.filters.manualSearchString | (state) => state.filters.manualSearchString |
| iconProfileColor: "#C4C4C4", | iconProfileColor: "#C4C4C4", | ||||
| skeletonItemColor: "#F4F4F4", | skeletonItemColor: "#F4F4F4", | ||||
| chatHeaderColor: "#F4F4F4", | chatHeaderColor: "#F4F4F4", | ||||
| messageBackground: "#E4E4E4", | |||||
| messageBackground: "#F4F4F4", | |||||
| messageText: "#1D1D1D", | messageText: "#1D1D1D", | ||||
| messageDate: "#939393", | |||||
| messageDate: "#949494", | |||||
| messageMyDate: "#C4C4C4", | messageMyDate: "#C4C4C4", | ||||
| filterSkeletonBackground: "#E4E4E4", | filterSkeletonBackground: "#E4E4E4", | ||||
| filterSkeletonBackgroundSecond: "#D4D4D4", | filterSkeletonBackgroundSecond: "#D4D4D4", |