Selaa lähdekoodia

Merge branch 'master' of https://git.dilig.net/selenaaasi/trampa-frontend into bugfix/1574

bugfix/1574
jovan.cirkovic 3 vuotta sitten
vanhempi
commit
d599ffb160
48 muutettua tiedostoa jossa 851 lisäystä ja 179 poistoa
  1. 1
    1
      package.json
  2. 1
    1
      public/index.html
  3. 3
    0
      src/assets/images/svg/accept.svg
  4. 5
    0
      src/assets/images/svg/info-circled.svg
  5. 9
    9
      src/components/Cards/CreateOfferCard/FirstPart/OfferCategoryField/OfferCategoryField.js
  6. 21
    24
      src/components/Cards/MessageCard/MessageCard.styled.js
  7. 2
    2
      src/components/Cards/OfferCard/CheckButton/CheckButton.styled.js
  8. 42
    8
      src/components/Cards/OfferCard/OfferCard.js
  9. 26
    1
      src/components/Cards/OfferCard/OfferCard.styled.js
  10. 87
    0
      src/components/Cards/RequestExchangeCard/RequestExchangeCard.js
  11. 42
    0
      src/components/Cards/RequestExchangeCard/RequestExchangeCard.styled.js
  12. 84
    0
      src/components/Cards/RequestExchangeCard/RequestExchangeMessage/RequestExchangeMessage.js
  13. 36
    0
      src/components/Cards/RequestExchangeCard/RequestExchangeMessage/RequestExchangeMessage.styled.js
  14. 92
    1
      src/components/DirectChat/DirectChat.js
  15. 25
    7
      src/components/DirectChat/DirectChatContent/DirectChatContent.js
  16. 7
    1
      src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js
  17. 60
    41
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js
  18. 14
    1
      src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js
  19. 9
    2
      src/components/DirectChat/DirectChatHeader/DirectChatHeader.js
  20. 3
    3
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.js
  21. 1
    1
      src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js
  22. 1
    1
      src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js
  23. 14
    0
      src/components/MarketPlace/MarketPlace.js
  24. 7
    6
      src/components/Profile/ProfileOffers/ProfileOffers.js
  25. 11
    2
      src/components/Profile/ProfileOffers/SelectSortField/SelectSortField.js
  26. 7
    0
      src/constants/exchangeStatus.js
  27. 5
    0
      src/constants/requesterStatus.js
  28. 8
    0
      src/i18n/resources/rs.js
  29. 1
    0
      src/pages/ChatMessages/ChatMessages.styled.js
  30. 1
    0
      src/request/apiEndpoints.js
  31. 14
    6
      src/request/exchangeRequest.js
  32. 1
    0
      src/request/index.js
  33. 12
    0
      src/socket/socket.js
  34. 1
    0
      src/store/actions/chat/chatActionConstants.js
  35. 5
    0
      src/store/actions/chat/chatActions.js
  36. 9
    0
      src/store/actions/exchange/exchangeActionConstants.js
  37. 46
    21
      src/store/actions/exchange/exchangeActions.js
  38. 1
    0
      src/store/middleware/accessTokensMiddleware.js
  39. 9
    0
      src/store/reducers/chat/chatReducer.js
  40. 11
    2
      src/store/reducers/exchange/exchangeReducer.js
  41. 11
    0
      src/store/reducers/filters/filtersReducer.js
  42. 19
    2
      src/store/saga/chatSaga.js
  43. 56
    28
      src/store/saga/exchangeSaga.js
  44. 17
    6
      src/store/saga/offersSaga.js
  45. 4
    0
      src/store/selectors/chatSelectors.js
  46. 4
    0
      src/store/selectors/exchangeSelector.js
  47. 4
    0
      src/store/selectors/filtersSelectors.js
  48. 2
    2
      src/themes/primaryTheme/primaryThemeColors.js

+ 1
- 1
package.json Näytä tiedosto

@@ -1,6 +1,6 @@
{
"name": "web",
"version": "4.0.5",
"version": "4.0.9",
"private": true,
"dependencies": {
"@emotion/react": "^11.5.0",

+ 1
- 1
public/index.html Näytä tiedosto

@@ -6,7 +6,7 @@
<link
rel="stylesheet"
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
rel="stylesheet"

+ 3
- 0
src/assets/images/svg/accept.svg Näytä tiedosto

@@ -0,0 +1,3 @@
<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>

+ 5
- 0
src/assets/images/svg/info-circled.svg Näytä tiedosto

@@ -0,0 +1,5 @@
<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>

+ 9
- 9
src/components/Cards/CreateOfferCard/FirstPart/OfferCategoryField/OfferCategoryField.js Näytä tiedosto

@@ -3,16 +3,16 @@ import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { FieldLabel, SelectOption } from "../FirstPartCreateOffer.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 { t } = useTranslation();
const { isMobile } = useIsMobile();
// const { isMobile } = useIsMobile();
const formik = props.formik;
return (
<>
@@ -35,9 +35,9 @@ const OfferCategoryField = (props) => {
onClick={() => props.handleSubcategories(cat.name)}
>
<CategoryContainer>
<CategoryIcon
{/* <CategoryIcon
src={getImageUrl(cat.image, variants.categoryIcon, isMobile)}
/>
/> */}
{cat.name}
</CategoryContainer>
</SelectOption>

+ 21
- 24
src/components/Cards/MessageCard/MessageCard.styled.js Näytä tiedosto

@@ -4,7 +4,7 @@ import selectedTheme from "../../../themes";

export const MessageCardContainer = styled(Box)`
display: flex;
flex-direction: ${props => props.ismymessage ? `row-reverse` : `row`};
flex-direction: ${(props) => (props.ismymessage ? `row-reverse` : `row`)};
margin-bottom: 18px;
`;
export const ProfileImage = styled.img`
@@ -18,33 +18,30 @@ export const ProfileImage = styled.img`
}
`;
export const MessageContent = styled(Box)`
background-color: ${(props) =>
props.ismymessage
? selectedTheme.colors.primaryPurple
: selectedTheme.colors.messageBackground};
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%;
}
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: ${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)`
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;
`;

+ 2
- 2
src/components/Cards/OfferCard/CheckButton/CheckButton.styled.js Näytä tiedosto

@@ -9,8 +9,8 @@ export const CheckButtonContainer = styled(PrimaryButton)`
bottom: 9px;
right: 9px;
&:hover button {
background-color: ${selectedTheme.colors.primaryPurple} !important;
color: white !important;
background-color: ${props => !props.disabled && `${selectedTheme.colors.primaryPurple} !important`};
color: ${props => !props.disabled && `white !important`};
}
@media (max-width: 1150px) {
display: none;

+ 42
- 8
src/components/Cards/OfferCard/OfferCard.js Näytä tiedosto

@@ -34,6 +34,8 @@ import {
ButtonsContainer,
TooltipInnerContainer,
UnpinOutlinedIcon,
AcceptIconContainer,
AcceptIcon,
} from "./OfferCard.styled";
import { ReactComponent as Message } from "../../../assets/images/svg/mail.svg";
import { useHistory } from "react-router-dom";
@@ -55,6 +57,7 @@ import {
ADMIN_ITEM_DETAILS_PAGE,
ITEM_DETAILS_PAGE,
} from "../../../constants/pages";
import exchangeStatus from "../../../constants/exchangeStatus";

const OfferCard = (props) => {
const dispatch = useDispatch();
@@ -97,7 +100,6 @@ const OfferCard = (props) => {
props.messageUser(props.offer);
};
const makeReview = (event) => {
console.log("EVEEEEEEENT: ", event);
event.stopPropagation();
if (!props.disabledReviews) {
props.makeReview(props.offer);
@@ -124,6 +126,12 @@ const OfferCard = (props) => {
);
};

const acceptExchange = (event) => {
event.stopPropagation();
console.log("props exchange");
props.acceptExchange();
};

const showMessageIcon = useMemo(() => {
if (userId === props.offer?.userId) {
return false;
@@ -131,6 +139,8 @@ const OfferCard = (props) => {
return true;
}, [userId, props.offer]);

console.log("props", props);

return (
<React.Fragment>
<OfferCardContainer
@@ -260,13 +270,35 @@ const OfferCard = (props) => {
</Tooltip>
</>
) : 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 ||
// props.disabledReviews
// }
disabled={
props.exchangeState === exchangeStatus.I_OFFERED ||
props.disabledReviews
}
onClick={acceptExchange}
>
<AcceptIcon
disabled={
props.disabledReviews ||
props.exchangeState === exchangeStatus.I_OFFERED
}
/>
</AcceptIconContainer>
)
) : (
<Tooltip title={t("messages.tooltip")}>
<TooltipInnerContainer>
@@ -339,6 +371,8 @@ OfferCard.propTypes = {
skeleton: PropTypes.bool,
disabledCheckButton: PropTypes.bool,
isAdmin: PropTypes.bool,
exchangeState: PropTypes.any,
acceptExchange: PropTypes.func,
};
OfferCard.defaultProps = {
halfwidth: false,

+ 26
- 1
src/components/Cards/OfferCard/OfferCard.styled.js Näytä tiedosto

@@ -12,6 +12,7 @@ import { ReactComponent as PinOutlined } from "../../../assets/images/svg/pin-ou
import { ReactComponent as UnpinOutlined } from "../../../assets/images/svg/unpin-outlined.svg";
import { ReactComponent as Category } from "../../../assets/images/svg/category.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)`
display: ${(props) => (props.skeleton ? "none" : "flex")};
@@ -365,6 +366,18 @@ export const LikeIconContainer = styled(MessageIcon)`
}
`}
`;
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)`
@media (max-width: 600px) {
${(props) =>
@@ -382,7 +395,19 @@ export const LikeIcon = styled(Like)`
& path {
stroke: ${(props) =>
props.disabled
? selectedTheme.colors.primaryPurpleDisabled
? selectedTheme.colors.primaryPurple
: selectedTheme.colors.primaryPurple};
}
@media (max-width: 600px) {
position: relative;
top: -1px;
}
`;
export const AcceptIcon = styled(Accept)`
& path {
fill: ${(props) =>
props.disabled
? selectedTheme.colors.primaryPurple
: selectedTheme.colors.primaryPurple};
}
@media (max-width: 600px) {

+ 87
- 0
src/components/Cards/RequestExchangeCard/RequestExchangeCard.js Näytä tiedosto

@@ -0,0 +1,87 @@
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;

+ 42
- 0
src/components/Cards/RequestExchangeCard/RequestExchangeCard.styled.js Näytä tiedosto

@@ -0,0 +1,42 @@
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)``;

+ 84
- 0
src/components/Cards/RequestExchangeCard/RequestExchangeMessage/RequestExchangeMessage.js Näytä tiedosto

@@ -0,0 +1,84 @@
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;

+ 36
- 0
src/components/Cards/RequestExchangeCard/RequestExchangeMessage/RequestExchangeMessage.styled.js Näytä tiedosto

@@ -0,0 +1,36 @@
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;
}
`;

+ 92
- 1
src/components/DirectChat/DirectChat.js Näytä tiedosto

@@ -22,9 +22,24 @@ import SkeletonDirectChat from "./SkeletonDirectChat/SkeletonDirectChat";
import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors";
import { CHAT_SCOPE } from "../../store/actions/chat/chatActionConstants";
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 { 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 chat = useSelector(selectSelectedChat);
@@ -34,6 +49,8 @@ const DirectChat = () => {
const location = useLocation();
const dispatch = useDispatch();
const { t } = useTranslation();
const exchange = useSelector(selectExchange);
const requester = useSelector(selectRequester);

const userId = useSelector(selectUserId);
const isLoadingDirectChat = useSelector(
@@ -54,6 +71,43 @@ const DirectChat = () => {
return chat;
}, [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(() => {
if (location?.state?.offerId) {
return {
@@ -96,6 +150,12 @@ const DirectChat = () => {
message: data.message,
})
);
if (
data.message?.isAcceptRequest &&
requester === requesterStatus.NOONE
) {
dispatch(setRequester(requesterStatus.INTERLUCATOR));
}
} else {
dispatch(fetchChats());
}
@@ -116,6 +176,34 @@ const DirectChat = () => {
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 (
<DirectChatContainer>
{isLoadingDirectChat || isLoadingDirectChat === undefined ? (
@@ -124,14 +212,17 @@ const DirectChat = () => {
<>
<DirectChatHeaderTitle />
<DirectChatHeader
exchangeState={exchangeState}
offer={offerObject}
interlocutor={interlocutorObject}
acceptExchange={handleAcceptExchange}
/>
</>
)}

<DirectChatContent
chat={chatObject}
exchangeState={exchangeState}
interlucator={interlocutorObject}
refreshChat={refreshChat}
/>

+ 25
- 7
src/components/DirectChat/DirectChatContent/DirectChatContent.js Näytä tiedosto

@@ -14,11 +14,15 @@ import DirectChatNewMessage from "../DirectChatNewMessage/DirectChatNewMessage";
import SkeletonDirectChatContent from "./SkeletonDirectChatContent/SkeletonDirectChatContent";
import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSelectors";
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 userId = useSelector(selectUserId);
const myProfileImage = useSelector(selectMineProfilePicture);
const messagesRef = useRef(null);
const requester = useSelector(selectRequester);
const interlucatorProfileImage = props?.interlucator?.image;
const isLoadingChatContent = useSelector(
selectIsLoadingByActionType(CHAT_SCOPE)
@@ -41,20 +45,33 @@ const DirectChatContent = (props) => {
<SkeletonDirectChatContent />
) : (
<DirectChatContentContainer>
<DirectChatContentHeader interlucator={props?.interlucator} />
<MessagesList ref={messagesRef}>
<DirectChatContentHeader
interlucator={props?.interlucator}
exchangeState={props.exchangeState}
/>
<MessagesList ref={messagesRef} exchangeState={props?.exchangeState}>
{messages?.map((item) => {
const isMyMessage = userId === item.userId;
const image = isMyMessage
? myProfileImage
: interlucatorProfileImage;
if (requester === requesterStatus.INTERLUCATOR && isMyMessage && item?.isAcceptRequest)
return;
return (
<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>
);
})}
@@ -75,6 +92,7 @@ DirectChatContent.propTypes = {
chat: PropTypes.any,
interlucator: PropTypes.any,
refreshChat: PropTypes.func,
exchangeState: PropTypes.any,
};

export default DirectChatContent;

+ 7
- 1
src/components/DirectChat/DirectChatContent/DirectChatContent.styled.js Näytä tiedosto

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

export const DirectChatContentContainer = styled(Box)`
@@ -14,7 +15,12 @@ export const MessagesList = styled(Box)`
padding: 18px 36px;
position: relative;
max-height: 425px;
height: 425px;
height: ${(props) =>
props.exchangeState === exchangeStatus.ACCEPTED ||
props.exchangeState === exchangeStatus.I_OFFERED ||
props.exchangeState === exchangeStatus.REVIEWED
? "385px"
: "425px"};
overflow-y: auto;
display: flex;
flex-direction: column;

+ 60
- 41
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.js Näytä tiedosto

@@ -3,6 +3,8 @@ import PropTypes from "prop-types";
import {
DirectChatContentHeaderContainer,
DirectChatContentHeaderFlexContainer,
DirectChatHeaderStatusContainer,
DirectChatHeaderStatusText,
PhoneIcon,
PhoneIconContainer,
ProfileDetails,
@@ -21,8 +23,11 @@ import { replaceInRoute } from "../../../../util/helpers/routeHelpers";
import { PROFILE_PAGE } from "../../../../constants/pages";
import { selectAmIBlocked } from "../../../../store/selectors/profileSelectors";
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import exchangeStatus from "../../../../constants/exchangeStatus";

const DirectChatContentHeader = (props) => {
const { t } = useTranslation();
const [showPhonePopover, setShowPhonePopover] = useState(false);
const [phonePopoverAnchorEl, setPhonePopoverAnchorEl] = useState(null);
const { isMobile } = useIsMobile();
@@ -46,54 +51,68 @@ const DirectChatContentHeader = (props) => {
);
};
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 = {
children: PropTypes.node,
interlucator: PropTypes.any,
exchangeState: PropTypes.bool,
};

export default DirectChatContentHeader;

+ 14
- 1
src/components/DirectChat/DirectChatContent/DirectChatContentHeader/DirectChatContentHeader.styled.js Näytä tiedosto

@@ -1,4 +1,4 @@
import { Box } from "@mui/material";
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";
@@ -91,3 +91,16 @@ export const PhoneIconContainer = styled(IconButton)`
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;
`;

+ 9
- 2
src/components/DirectChat/DirectChatHeader/DirectChatHeader.js Näytä tiedosto

@@ -29,8 +29,7 @@ const DirectChatHeader = (props) => {
return false;
}, [exchange, userId]);

const showReviewModal = (event) => {
event.stopPropagation();
const showReviewModal = () => {
dispatch(
toggleCreateReviewModal({
offer: props.offer,
@@ -43,6 +42,10 @@ const DirectChatHeader = (props) => {
const refetchExchange = () => {
dispatch(fetchExchange(chat.chat.exchangeId));
};

const acceptExchange = () => {
props.acceptExchange();
};
return (
<DirectChatHeaderContainer>
<OfferCard
@@ -50,6 +53,8 @@ const DirectChatHeader = (props) => {
aboveChat
disabledReviews={props.interlocutor?._blocked || isDisabledReviews}
makeReview={showReviewModal}
acceptExchange={acceptExchange}
exchangeState={props?.exchangeState}
dontShowViews
disabledCheckButton={
props.interlocutor?._blocked || props?.offer?._deleted
@@ -63,6 +68,8 @@ DirectChatHeader.propTypes = {
children: PropTypes.node,
offer: PropTypes.any,
interlocutor: PropTypes.any,
acceptExchange: PropTypes.func,
exchangeState: PropTypes.any,
};

export default DirectChatHeader;

+ 3
- 3
src/components/DirectChat/MiniChatColumn/MiniChatColumn.js Näytä tiedosto

@@ -8,6 +8,7 @@ import {
import MiniChatCard from "../../Cards/MiniChatCard/MiniChatCard";
import { useDispatch, useSelector } from "react-redux";
import {
selectChatsTotal,
selectLatestChats,
selectSelectedChat,
} from "../../../store/selectors/chatSelectors";
@@ -23,6 +24,7 @@ import Paging from "../../Paging/Paging";

const MiniChatColumn = () => {
const chats = useSelector(selectLatestChats);
const total = useSelector(selectChatsTotal);
const selectedChat = useSelector(selectSelectedChat);
const offer = useSelector(selectOffer);
const location = useLocation();
@@ -79,9 +81,7 @@ const MiniChatColumn = () => {
elementsPerPage={6}
customPaging
current={paging.currentPage}
pages={
chats.length === 6 ? paging.currentPage + 1 : paging.currentPage
}
totalElements={total}
changePage={handleChangePage}
>
<ChatPagingText>

+ 1
- 1
src/components/DirectChat/MiniChatColumn/MiniChatColumn.styled.js Näytä tiedosto

@@ -5,7 +5,7 @@ import selectedTheme from "../../../themes";
export const MiniChatColumnContainer = styled(Box)`
position: relative;
padding-bottom: 50px;
@media (max-width: 600px) {
@media (max-width: 900px) {
display: none;
}
`;

+ 1
- 1
src/components/ItemDetails/ItemDetailsHeaderCard/ItemDetailsHeaderCard.js Näytä tiedosto

@@ -86,7 +86,7 @@ const ItemDetailsHeaderCard = (props) => {
) : (
<Tooltip title={t("messages.tooltip")}>
<TooltipInnerContainer>
<MessageIcon disabled onClick={() => messageUser(offer)}>
<MessageIcon onClick={() => messageUser(offer)}>
<MessageColor />
</MessageIcon>
</TooltipInnerContainer>

+ 14
- 0
src/components/MarketPlace/MarketPlace.js Näytä tiedosto

@@ -3,9 +3,23 @@ import PropTypes from "prop-types";
import { MarketPlaceContainer } from "./MarketPlace.styled";
import Header from "./Header/Header";
import Offers from "./Offers/Offers";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { fetchChats } from "../../store/actions/chat/chatActions";
import { selectUserId } from "../../store/selectors/loginSelectors";

const MarketPlace = (props) => {
const [isGrid, setIsGrid] = useState(false);
const userId = useSelector(selectUserId);
const dispatch = useDispatch();
useEffect(() => {
if (userId)
dispatch(
fetchChats({
currentPage: 1,
})
);
}, [userId]);
const offers = props.offers;
return (
<MarketPlaceContainer>

+ 7
- 6
src/components/Profile/ProfileOffers/ProfileOffers.js Näytä tiedosto

@@ -41,6 +41,7 @@ const ProfileOffers = (props) => {
selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE)
);
const chats = useSelector(selectLatestChats);
const sortRef = useRef(null);
const profileOffers = useSelector(selectProfileOffers);
const { isMobile } = useIsMobile();
const userId = useSelector(selectUserId);
@@ -67,12 +68,11 @@ const ProfileOffers = (props) => {
}, [searchValue, sortOption, paging.currentPage]);

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]);

@@ -122,6 +122,7 @@ const ProfileOffers = (props) => {
) : (
<OffersContainer>
<SelectSortField
ref={sortRef}
offersToShow={profileOffers}
setSortOption={handleChangeSortOption}
/>

+ 11
- 2
src/components/Profile/ProfileOffers/SelectSortField/SelectSortField.js Näytä tiedosto

@@ -7,10 +7,17 @@ import {
} from "./SelectSortField.styled";
import { sortEnum } from "../../../../enums/sortEnum";
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);

useImperativeHandle(ref, () => ({
setSortOption,
sortOption
}))

const handleChangeSelect = (event) => {
let chosenOption;
for (const sortOption in sortEnum) {
@@ -47,7 +54,9 @@ const SelectSortField = (props) => {
})}
</HeaderSelect>
);
};
});

SelectSortField.displayName = "SelectSortField";

SelectSortField.propTypes = {
offersToShow: PropTypes.array,

+ 7
- 0
src/constants/exchangeStatus.js Näytä tiedosto

@@ -0,0 +1,7 @@
export default {
INITIAL: 0,
I_OFFERED: 1,
I_AM_OFFERED: 2,
ACCEPTED: 3, // NOT REVIEWED
REVIEWED: 4,
}

+ 5
- 0
src/constants/requesterStatus.js Näytä tiedosto

@@ -0,0 +1,5 @@
export default {
NOONE: 0,
ME: 1,
INTERLUCATOR: 2
}

+ 8
- 0
src/i18n/resources/rs.js Näytä tiedosto

@@ -254,6 +254,14 @@ export default {
noMessages: "Poruke nisu pronađene.",
noMessagesSecond: "Nažalost, nemate ni jednu 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: {
website: "Web Sajt",

+ 1
- 0
src/pages/ChatMessages/ChatMessages.styled.js Näytä tiedosto

@@ -3,6 +3,7 @@ import styled from "styled-components";
import { transitionOnLoadFromRight } from "../../components/Styles/globalStyleComponents";

export const ChatMessagesPageContainer = styled(Container)`
max-width: none;
@media (max-width: 600px) {
animation: 0.2s ease 0s 1 ${transitionOnLoadFromRight};
}

+ 1
- 0
src/request/apiEndpoints.js Näytä tiedosto

@@ -190,6 +190,7 @@ export default {
exchange: {
getExchange: "exchanges",
validateExchange: "exchanges",
acceptExchange: "users/{userId}/exchanges/{exchangeId}/accept"
},
reviews: {
getUserReviews: "/users/{userId}/reviews",

+ 14
- 6
src/request/exchangeRequest.js Näytä tiedosto

@@ -1,10 +1,18 @@
import { getRequest, patchRequest } from "."
import apiEndpoints from "./apiEndpoints"
import { getRequest, patchRequest, replaceInUrl } from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchExchange = (payload) => {
return getRequest(apiEndpoints.exchange.getExchange + `/${payload}`);
}
return getRequest(apiEndpoints.exchange.getExchange + `/${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,
})
);
};

+ 1
- 0
src/request/index.js Näytä tiedosto

@@ -5,6 +5,7 @@ const request = axios.create({
// baseURL: "http://192.168.88.150:3001/", // DJOLE
// baseURL: "http://192.168.88.175:3005/",
// baseURL: "http://192.168.88.143:3001/", // DULE
// baseURL: "https://trampa-api.dilig.net/",
baseURL: "https://trampa-api-test.dilig.net/",
// baseURL: "http://localhost:3001/",
// baseURL: process.env.REACT_APP_BASE_API_URL,

+ 12
- 0
src/socket/socket.js Näytä tiedosto

@@ -25,6 +25,18 @@ export const sendMessage = (chatId, userId, text, receiverUserId) => {
},
});
};
export const acceptExchangeSocket = (chatId, userId, receiverUserId, callbackFn) => {
socket.emit("private_message", {
chatId,
receiverUserId,
message: {
userId,
isAcceptRequest: true,
text: ""
},
});
callbackFn();
};

export const addMesageListener = (listener) => {
socket.on("private_message", (data) =>

+ 1
- 0
src/store/actions/chat/chatActionConstants.js Näytä tiedosto

@@ -32,6 +32,7 @@ export const CHAT_NEW_FETCH_SUCCESS = createSuccessType(CHAT_NEW_SCOPE);
export const CHAT_NEW_FETCH_ERROR = createErrorType(CHAT_NEW_SCOPE);

export const CHAT_SET = createSetType("CHAT_SET");
export const CHAT_TOTAL_SET = createSetType("CHAT_TOTAL_SET");
export const CHAT_ONE_SET = createSetType("CHAT_ONE_SET");
export const CHAT_CLEAR = createSetType("CHAT_CLEAR");
export const CHAT_ADD_MESSAGE = createSetType("CHAT_ADD_MESSAGE");

+ 5
- 0
src/store/actions/chat/chatActions.js Näytä tiedosto

@@ -19,6 +19,7 @@ import {
CHAT_SEND_FETCH,
CHAT_SEND_SUCCESS,
CHAT_SET,
CHAT_TOTAL_SET,
} from "./chatActionConstants";

export const fetchChats = (payload) => ({
@@ -33,6 +34,10 @@ export const setChats = (payload) => ({
type: CHAT_SET,
payload,
});
export const setChatsTotal = (payload) => ({
type: CHAT_TOTAL_SET,
payload,
});
export const fetchOneChat = (payload) => ({
type: CHAT_ONE_FETCH,
payload,

+ 9
- 0
src/store/actions/exchange/exchangeActionConstants.js Näytä tiedosto

@@ -18,5 +18,14 @@ export const EXCHANGE_VALIDATE_FETCH_SUCCESS = createSuccessType(
export const EXCHANGE_VALIDATE_FETCH_ERROR = createErrorType(
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 REQUESTER_SET = createSetType("REQUESTER_SET");

+ 46
- 21
src/store/actions/exchange/exchangeActions.js Näytä tiedosto

@@ -1,26 +1,51 @@
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) => ({
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 = () => ({
type: EXCHANGE_FETCH_SUCCESS
})
type: EXCHANGE_FETCH_SUCCESS,
});
export const fetchExchangeError = () => ({
type: EXCHANGE_FETCH_ERROR
})
type: EXCHANGE_FETCH_ERROR,
});
export const validateExchange = (payload) => ({
type: EXCHANGE_VALIDATE_FETCH,
payload,
});
export const validateExchangeSuccess = () => ({
type: EXCHANGE_VALIDATE_FETCH_SUCCESS
})
type: EXCHANGE_VALIDATE_FETCH_SUCCESS,
});
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,
});

+ 1
- 0
src/store/middleware/accessTokensMiddleware.js Näytä tiedosto

@@ -15,6 +15,7 @@ import { logoutUser, refreshUserToken } from "../actions/login/loginActions";
//Change URL with .env
// const baseURL = "http://192.168.88.143:3001/"; // DULE
// const baseURL = "http://192.168.88.175:3005/";
// const baseURL = "https://trampa-api.dilig.net/";
const baseURL = "https://trampa-api-test.dilig.net/";
// const baseURL = "http://192.168.88.150:3001/"; // DJOLE
// const baseURL = "http://localhost:3001/";

+ 9
- 0
src/store/reducers/chat/chatReducer.js Näytä tiedosto

@@ -3,17 +3,20 @@ import {
CHAT_CLEAR,
CHAT_ONE_SET,
CHAT_SET,
CHAT_TOTAL_SET,
} from "../../actions/chat/chatActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
latestChats: [],
selectedChat: {},
total: 0,
};

export default createReducer(
{
[CHAT_SET]: setChats,
[CHAT_TOTAL_SET]: setChatsTotal,
[CHAT_ONE_SET]: setOneChat,
[CHAT_CLEAR]: clearChats,
[CHAT_ADD_MESSAGE]: addNewMessage,
@@ -27,6 +30,12 @@ function setChats(state, action) {
latestChats: [...action.payload],
};
}
function setChatsTotal(state, action) {
return {
...state,
total: action.payload
};
}
function setOneChat(state, action) {
return {
...state,

+ 11
- 2
src/store/reducers/exchange/exchangeReducer.js Näytä tiedosto

@@ -1,13 +1,16 @@
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"

const initialState = {
exchange: {}
exchange: {},
requester: requesterStatus.NOONE,
}

export default createReducer(
{
[EXCHANGE_SET]: setExchange,
[REQUESTER_SET]: setRequester,
},
initialState
)
@@ -17,4 +20,10 @@ function setExchange(state, action) {
...state,
exchange: action.payload
}
}
function setRequester(state, action) {
return {
...state,
requester: action.payload
}
}

+ 11
- 0
src/store/reducers/filters/filtersReducer.js Näytä tiedosto

@@ -7,6 +7,7 @@ import {
SET_IS_APPLIED,
SET_LOCATIONS,
SET_MANUAL_SEARCH_STRING,
SET_QUERY_STRING,
SET_SEARCH_STRING,
SET_SORT_OPTION,
SET_SUBCATEGORY,
@@ -46,6 +47,7 @@ export default createReducer(
[SET_IS_APPLIED]: setIsAppliedStatus,
[SET_HEADER_STRING]: setHeaderString,
[SET_SEARCH_STRING]: setSearchString,
[SET_QUERY_STRING]: setQueryString,
[SET_MANUAL_SEARCH_STRING]: setManualSearchString,
},
initialState
@@ -146,3 +148,12 @@ function setSearchString(state, { payload }) {
},
};
}
function setQueryString(state, { payload }) {
return {
...state,
filters: {
...state.filters,
queryString: payload,
},
};
}

+ 19
- 2
src/store/saga/chatSaga.js Näytä tiedosto

@@ -24,11 +24,15 @@ import {
sendMessageError,
sendMessageSuccess,
setChats,
setChatsTotal,
setOneChat,
startNewChatError,
startNewChatSuccess,
} from "../actions/chat/chatActions";
import { validateExchange } from "../actions/exchange/exchangeActions";
import {
setRequester,
validateExchange,
} from "../actions/exchange/exchangeActions";
import { selectSelectedChat } from "../selectors/chatSelectors";
import { selectExchange } from "../selectors/exchangeSelector";
import { selectUserId } from "../selectors/loginSelectors";
@@ -36,6 +40,7 @@ import { sendMessage as SendMessageSocket } from "../../socket/socket";
import { KEY_PAGE, KEY_SIZE } from "../../constants/queryStringConstants";
import { setQueryStringRedux } from "../actions/queryString/queryStringActions";
import { selectQueryString } from "../selectors/queryStringSelectors";
import requesterStatus from "../../constants/requesterStatus";

function* fetchChats({ payload }) {
try {
@@ -56,6 +61,7 @@ function* fetchChats({ payload }) {
});
yield call(console.dir, data);
yield put(setChats([...data.data.chatsWithData]));
yield put(setChatsTotal(data.data.total))
yield put(fetchChatsSuccess());
} catch (e) {
yield put(fetchChatsError());
@@ -81,6 +87,14 @@ function* fetchOneChat(payload) {
chatId: payload.payload,
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 = {
chat: chatData.data.chat,
offer: {
@@ -88,6 +102,7 @@ function* fetchOneChat(payload) {
},
interlocutor: chatData.data.interlocutorData,
};
yield put(setRequester(requester));
yield put(setOneChat(chat));
yield put(fetchOneChatSuccess());
} catch (e) {
@@ -127,7 +142,9 @@ function* startNewChat(payload) {
});
const queryString = yield select(selectQueryString);
const data = yield call(attemptFetchChats, { userId, queryString });
yield put(setChats([...data.data]));
yield put(setChats([...data.data.chatsWithData]));
yield put(setChatsTotal(data.data.total))
console.log(data);
const newChatId = newChatData.data.chatId;
yield put(startNewChatSuccess());
yield call(

+ 56
- 28
src/store/saga/exchangeSaga.js Näytä tiedosto

@@ -1,36 +1,64 @@
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) {
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) {
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() {
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),
]);
}

+ 17
- 6
src/store/saga/offersSaga.js Näytä tiedosto

@@ -89,6 +89,8 @@ import {
KEY_SIZE,
KEY_SORTBY,
} from "../../constants/queryStringConstants";
import { selectQueryString } from "../selectors/filtersSelectors";
import { setQueryString } from "../actions/filters/filtersActions";

function* fetchOffers(payload) {
try {
@@ -233,15 +235,24 @@ function* fetchMineHeaderOffers() {
function* fetchProfileOffers(payload) {
try {
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)
throw new Error("User id is not defined!");
const queryString = new URLSearchParams();
let queryString = new URLSearchParams();
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(
attemptFetchProfileOffers,
userId,

+ 4
- 0
src/store/selectors/chatSelectors.js Näytä tiedosto

@@ -6,6 +6,10 @@ export const selectLatestChats = createSelector(
chatSelector,
(state) => state.latestChats
)
export const selectChatsTotal = createSelector(
chatSelector,
(state) => state.total
)
export const selectSelectedChat = createSelector(
chatSelector,
(state) => state.selectedChat

+ 4
- 0
src/store/selectors/exchangeSelector.js Näytä tiedosto

@@ -5,4 +5,8 @@ const exchangeSelector = (state) => state.exchange;
export const selectExchange = createSelector(
exchangeSelector,
(state) => state.exchange
)
export const selectRequester = createSelector(
exchangeSelector,
(state) => state.requester
)

+ 4
- 0
src/store/selectors/filtersSelectors.js Näytä tiedosto

@@ -38,6 +38,10 @@ export const selectSearchString = createSelector(
filtersSelector,
(state) => state.filters.searchString
);
export const selectQueryString = createSelector(
filtersSelector,
(state) => state.filters.queryString
);
export const selectManualSearchString = createSelector(
filtersSelector,
(state) => state.filters.manualSearchString

+ 2
- 2
src/themes/primaryTheme/primaryThemeColors.js Näytä tiedosto

@@ -28,9 +28,9 @@ export const primaryThemeColors = {
iconProfileColor: "#C4C4C4",
skeletonItemColor: "#F4F4F4",
chatHeaderColor: "#F4F4F4",
messageBackground: "#E4E4E4",
messageBackground: "#F4F4F4",
messageText: "#1D1D1D",
messageDate: "#939393",
messageDate: "#949494",
messageMyDate: "#C4C4C4",
filterSkeletonBackground: "#E4E4E4",
filterSkeletonBackgroundSecond: "#D4D4D4",

Loading…
Peruuta
Tallenna