Added MMS support for messaging. IO-538
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { PictureFilled } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import { Badge, Popover } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
||||
|
||||
export function ChatMediaSelector({
|
||||
selectedMedia,
|
||||
setSelectedMedia,
|
||||
conversation,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
console.log("conversation", conversation);
|
||||
const { loading, error, data, refetch } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||
variables: {
|
||||
jobId:
|
||||
conversation.job_conversations[0] &&
|
||||
conversation.job_conversations[0].jobid,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
skip:
|
||||
!conversation.job_conversations ||
|
||||
conversation.job_conversations.length === 0,
|
||||
});
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleVisibleChange = (visible) => {
|
||||
setVisible(visible);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedMedia([]);
|
||||
}, [setSelectedMedia, conversation]);
|
||||
|
||||
const content = (
|
||||
<div>
|
||||
{loading && <LoadingSpinner />}
|
||||
{error && <AlertComponent message={error.message} type="error" />}
|
||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||
) : null}
|
||||
{data && (
|
||||
<JobDocumentsGalleryExternal
|
||||
data={data ? data.documents : []}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
conversation.job_conversations.length === 0 ? (
|
||||
<div>{t("messaging.errors.noattachedjobs")}</div>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
title={t("messaging.labels.selectmedia")}
|
||||
trigger="click"
|
||||
visible={visible}
|
||||
onVisibleChange={handleVisibleChange}
|
||||
>
|
||||
<Badge
|
||||
size="small"
|
||||
count={selectedMedia.filter((s) => s.isSelected).length}
|
||||
>
|
||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||
</Badge>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||
import {
|
||||
@@ -8,8 +10,6 @@ import {
|
||||
List,
|
||||
} from "react-virtualized";
|
||||
import "./chat-message-list.styles.scss";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
|
||||
export default function ChatMessageListComponent({ messages }) {
|
||||
const virtualizedListRef = useRef(null);
|
||||
@@ -46,6 +46,16 @@ export default function ChatMessageListComponent({ messages }) {
|
||||
{MessageRender(messages[index])}
|
||||
{StatusRender(messages[index].status)}
|
||||
</div>
|
||||
{messages[index].isoutbound && (
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: messages[index].userid,
|
||||
time: moment(messages[index].created_at).format(
|
||||
"MM/DD/YYYY @ hh:mm a"
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
@@ -74,27 +84,19 @@ export default function ChatMessageListComponent({ messages }) {
|
||||
}
|
||||
|
||||
const MessageRender = (message) => {
|
||||
if (message.image) {
|
||||
return (
|
||||
<a href={message.image_path} target="__blank">
|
||||
<img alt="Received" className="message-img" src={message.image_path} />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div>{message.text}</div>
|
||||
{message.isoutbound && (
|
||||
<div style={{ color: "slategray", fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: message.userid,
|
||||
time: moment(message.created_at).format("MM/DD/YYYY @ hh:mm a"),
|
||||
})}
|
||||
return (
|
||||
<div>
|
||||
{message.image_path &&
|
||||
message.image_path.map((i, idx) => (
|
||||
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<a href={i} target="__blank">
|
||||
<img alt="Received" className="message-img" src={i} />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
))}
|
||||
<div>{message.text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusRender = (status) => {
|
||||
|
||||
@@ -34,9 +34,10 @@
|
||||
//display: inline-block;
|
||||
|
||||
.message-img {
|
||||
max-width: 3rem;
|
||||
max-height: 3rem;
|
||||
max-width: 10rem;
|
||||
max-height: 10rem;
|
||||
object-fit: contain;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Input, Spin } from "antd";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
selectMessage,
|
||||
} from "../../redux/messaging/messaging.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
|
||||
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -36,6 +37,7 @@ function ChatSendMessageComponent({
|
||||
setMessage,
|
||||
}) {
|
||||
const inputArea = useRef(null);
|
||||
const [selectedMedia, setSelectedMedia] = useState([]);
|
||||
useEffect(() => {
|
||||
inputArea.current.focus();
|
||||
}, [isSending, setMessage]);
|
||||
@@ -43,36 +45,55 @@ function ChatSendMessageComponent({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleEnter = () => {
|
||||
if (message === "" || !message) return;
|
||||
logImEXEvent("messaging_send_message");
|
||||
sendMessage({
|
||||
to: conversation.phone_num,
|
||||
body: message,
|
||||
messagingServiceSid: bodyshop.messagingservicesid,
|
||||
conversationid: conversation.id,
|
||||
});
|
||||
const selectedImages = selectedMedia.filter((i) => i.isSelected);
|
||||
if (selectedImages < 11) {
|
||||
sendMessage({
|
||||
to: conversation.phone_num,
|
||||
body: message,
|
||||
messagingServiceSid: bodyshop.messagingservicesid,
|
||||
conversationid: conversation.id,
|
||||
selectedMedia: selectedImages,
|
||||
});
|
||||
setSelectedMedia(
|
||||
selectedMedia.map((i) => {
|
||||
return { ...i, isSelected: false };
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||
<ChatPresetsComponent className="imex-flex-row__margin" />
|
||||
|
||||
<Input.TextArea
|
||||
className="imex-flex-row__margin imex-flex-row__grow"
|
||||
allowClear
|
||||
autoFocus
|
||||
ref={inputArea}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={message}
|
||||
disabled={isSending}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!!!event.shiftKey) handleEnter();
|
||||
}}
|
||||
<ChatMediaSelector
|
||||
conversation={conversation}
|
||||
selectedMedia={selectedMedia}
|
||||
setSelectedMedia={setSelectedMedia}
|
||||
/>
|
||||
<span style={{ flex: 1 }}>
|
||||
<Input.TextArea
|
||||
className="imex-flex-row__margin imex-flex-row__grow"
|
||||
allowClear
|
||||
autoFocus
|
||||
ref={inputArea}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={message}
|
||||
disabled={isSending}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!!!event.shiftKey) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<SendOutlined
|
||||
className="imex-flex-row__margin"
|
||||
disabled={message === "" || !message}
|
||||
onClick={handleEnter}
|
||||
/>
|
||||
|
||||
<SendOutlined className="imex-flex-row__margin" onClick={handleEnter} />
|
||||
<Spin
|
||||
style={{ display: `${isSending ? "" : "none"}` }}
|
||||
indicator={
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import React from "react";
|
||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Gallery from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
function JobsDocumentGalleryExternal({
|
||||
data,
|
||||
|
||||
externalMediaState,
|
||||
}) {
|
||||
const [galleryImages, setgalleryImages] = externalMediaState;
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
let documents = data.reduce((acc, value) => {
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${value.key}`,
|
||||
thumbnail: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}`,
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
isSelected: false,
|
||||
key: value.key,
|
||||
extension: value.extension,
|
||||
id: value.id,
|
||||
type: value.type,
|
||||
tags: [{ value: value.type, title: value.type }],
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
setgalleryImages(documents);
|
||||
}, [data, setgalleryImages, t]);
|
||||
|
||||
return (
|
||||
<div className="clearfix">
|
||||
<Gallery
|
||||
images={galleryImages}
|
||||
backdropClosesModal={true}
|
||||
onSelectImage={(index, image) => {
|
||||
setgalleryImages(
|
||||
galleryImages.map((g, idx) =>
|
||||
index === idx ? { ...g, isSelected: !g.isSelected } : g
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default JobsDocumentGalleryExternal;
|
||||
@@ -1310,11 +1310,13 @@
|
||||
"new": "New Conversation"
|
||||
},
|
||||
"labels": {
|
||||
"maxtenimages": "You can only select up to a maximum of 10 images at a time.",
|
||||
"messaging": "Messaging",
|
||||
"noallowtxt": "This customer has not indicated their permission to be messaged.",
|
||||
"nojobs": "Not associated to any job.",
|
||||
"phonenumber": "Phone #",
|
||||
"presets": "Presets",
|
||||
"selectmedia": "Select Media",
|
||||
"sentby": "Sent by {{by}} at {{time}}",
|
||||
"typeamessage": "Send a message..."
|
||||
}
|
||||
|
||||
@@ -1310,11 +1310,13 @@
|
||||
"new": ""
|
||||
},
|
||||
"labels": {
|
||||
"maxtenimages": "",
|
||||
"messaging": "Mensajería",
|
||||
"noallowtxt": "",
|
||||
"nojobs": "",
|
||||
"phonenumber": "",
|
||||
"presets": "",
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Enviar un mensaje..."
|
||||
}
|
||||
|
||||
@@ -1310,11 +1310,13 @@
|
||||
"new": ""
|
||||
},
|
||||
"labels": {
|
||||
"maxtenimages": "",
|
||||
"messaging": "Messagerie",
|
||||
"noallowtxt": "",
|
||||
"nojobs": "",
|
||||
"phonenumber": "",
|
||||
"presets": "",
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Envoyer un message..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user