- {MessageRender(messages[index])}
- {StatusRender(messages[index].status)}
+
+
+
+
+ {message.image_path &&
+ message.image_path.map((i, idx) => (
+
+ ))}
+
{message.text}
- {messages[index].isoutbound && (
-
- {i18n.t("messaging.labels.sentby", {
- by: messages[index].userid,
- time: dayjs(messages[index].created_at).format("MM/DD/YYYY @ hh:mm a")
- })}
-
- )}
+
+ {message.status && (
+
+
+
+ )}
+
+ {message.isoutbound && (
+
+ {i18n.t("messaging.labels.sentby", {
+ by: message.userid,
+ time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a")
+ })}
)}
-
+
);
};
return (
-
- {({ height, width }) => (
-
- )}
-
+
renderMessage(index)}
+ followOutput="smooth" // Ensure smooth scrolling when new data is appended
+ style={{ height: "100%", width: "100%" }}
+ />
);
}
-
-const MessageRender = (message) => {
- return (
-
-
- {message.image_path &&
- message.image_path.map((i, idx) => (
-
- ))}
-
{message.text}
-
-
- );
-};
-
-const StatusRender = (status) => {
- switch (status) {
- case "sent":
- return
;
- case "delivered":
- return
;
- default:
- return null;
- }
-};
diff --git a/client/src/components/chat-messages-list/chat-message-list.styles.scss b/client/src/components/chat-messages-list/chat-message-list.styles.scss
index d576fa7b6..7958f02c3 100644
--- a/client/src/components/chat-messages-list/chat-message-list.styles.scss
+++ b/client/src/components/chat-messages-list/chat-message-list.styles.scss
@@ -1,37 +1,30 @@
.message-icon {
- //position: absolute;
- // bottom: 0rem;
color: whitesmoke;
border: #000000;
position: absolute;
margin: 0 0.1rem;
bottom: 0.1rem;
right: 0.3rem;
-
z-index: 5;
}
.chat {
flex: 1;
- //width: 300px;
- //border: solid 1px #eee;
display: flex;
flex-direction: column;
margin: 0.8rem 0rem;
+ overflow: hidden; // Ensure the content scrolls correctly
}
.messages {
- //margin-top: 30px;
display: flex;
flex-direction: column;
+ padding: 0.5rem; // Add padding to avoid edge clipping
}
.message {
border-radius: 20px;
padding: 0.25rem 0.8rem;
- //margin-top: 5px;
- // margin-bottom: 5px;
- //display: inline-block;
.message-img {
max-width: 10rem;
@@ -56,7 +49,7 @@
position: relative;
}
-.yours .message.last:before {
+.yours .message:last-child:before {
content: "";
position: absolute;
z-index: 0;
@@ -68,7 +61,7 @@
border-bottom-right-radius: 15px;
}
-.yours .message.last:after {
+.yours .message:last-child:after {
content: "";
position: absolute;
z-index: 1;
@@ -88,12 +81,11 @@
color: white;
margin-left: 25%;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
- background-attachment: fixed;
position: relative;
padding-bottom: 0.6rem;
}
-.mine .message.last:before {
+.mine .message:last-child:before {
content: "";
position: absolute;
z-index: 0;
@@ -102,11 +94,10 @@
height: 20px;
width: 20px;
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
- background-attachment: fixed;
border-bottom-left-radius: 15px;
}
-.mine .message.last:after {
+.mine .message:last-child:after {
content: "";
position: absolute;
z-index: 1;
diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx
index 06cd43d86..8ea784d64 100644
--- a/client/src/components/chat-popup/chat-popup.component.jsx
+++ b/client/src/components/chat-popup/chat-popup.component.jsx
@@ -39,7 +39,7 @@ export function ChatPopupComponent({
unreadCount
}) {
const { t } = useTranslation();
- const { socket, clientId } = useContext(SocketContext);
+ const { socket } = useContext(SocketContext);
// Emit event to open messaging when chat becomes visible
useEffect(() => {
diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx
index 918be1b5f..9e1710d0d 100644
--- a/client/src/components/chat-send-message/chat-send-message.component.jsx
+++ b/client/src/components/chat-send-message/chat-send-message.component.jsx
@@ -1,55 +1,66 @@
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Input, Spin } from "antd";
-import React, { useEffect, useRef, useState } from "react";
+import React, { useContext, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
-import { sendMessage, setMessage } from "../../redux/messaging/messaging.actions";
+import { setMessage } from "../../redux/messaging/messaging.actions";
import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.selectors";
-import { selectBodyshop } from "../../redux/user/user.selectors";
+import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
+import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
isSending: selectIsSending,
- message: selectMessage
+ message: selectMessage,
+ user: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
- sendMessage: (message) => dispatch(sendMessage(message)),
setMessage: (message) => dispatch(setMessage(message))
});
-function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
+function ChatSendMessageComponent({ conversation, bodyshop, isSending, message, setMessage, user }) {
+ const { socket } = useContext(SocketContext); // Access WebSocket instance
const inputArea = useRef(null);
const [selectedMedia, setSelectedMedia] = useState([]);
+ const { t } = useTranslation();
+
useEffect(() => {
inputArea.current.focus();
}, [isSending, setMessage]);
- const { t } = useTranslation();
-
const handleEnter = () => {
const selectedImages = selectedMedia.filter((i) => i.isSelected);
if ((message === "" || !message) && selectedImages.length === 0) return;
+
logImEXEvent("messaging_send_message");
if (selectedImages.length < 11) {
- sendMessage({
+ const messageData = {
+ user,
to: conversation.phone_num,
body: message || "",
messagingServiceSid: bodyshop.messagingservicesid,
conversationid: conversation.id,
selectedMedia: selectedImages,
imexshopid: bodyshop.imexshopid
- });
+ };
+
+ // Emit the send-message event via WebSocket
+ socket.emit("send-message", messageData);
+
setSelectedMedia(
selectedMedia.map((i) => {
return { ...i, isSelected: false };
})
);
+
+ // Optionally clear the input message
+ setMessage("");
}
};
@@ -74,15 +85,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
onChange={(e) => setMessage(e.target.value)}
onPressEnter={(event) => {
event.preventDefault();
- if (!!!event.shiftKey) handleEnter();
+ if (!event.shiftKey) handleEnter();
}}
/>
-
+
new Date().toLocaleTimeString("en-US", { hour12: true }).replace("AM", "a.m.").replace("PM", "p.m.");
-/** This is a hack around react-virtualized, should be removed when switching to react-virtuoso */
-const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`;
-
-function reactVirtualizedFix() {
- return {
- name: "flat:react-virtualized",
- configResolved: async () => {
- const require = createRequire(import.meta.url);
- const reactVirtualizedPath = require.resolve("react-virtualized");
- const { pathname: reactVirtualizedFilePath } = new url.URL(reactVirtualizedPath, import.meta.url);
- const file = reactVirtualizedFilePath.replace(
- path.join("dist", "commonjs", "index.js"),
- path.join("dist", "es", "WindowScroller", "utils", "onScroll.js")
- );
- const code = await fsPromises.readFile(file, "utf-8");
- const modified = code.replace(WRONG_CODE, "");
- await fsPromises.writeFile(file, modified);
- }
- };
-}
-/** End of hack */
-
export const logger = createLogger("info", {
allowClearScreen: false
});
@@ -108,7 +83,6 @@ export default defineConfig({
gcm_sender_id: "103953800507"
}
}),
- reactVirtualizedFix(),
react(),
eslint()
],
diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js
index 044e550b7..de2f8e25a 100644
--- a/server/web-sockets/redisSocketEvents.js
+++ b/server/web-sockets/redisSocketEvents.js
@@ -1,7 +1,14 @@
const { admin } = require("../firebase/firebase-handler");
const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries");
+const logger = require("../utils/logger");
+const { phone } = require("phone");
+const { client: gqlClient } = require("../graphql-client/graphql-client");
+const queries = require("../graphql-client/queries");
+const twilio = require("twilio");
const client = require("../graphql-client/graphql-client").client;
+const twilioClient = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY);
+
const redisSocketEvents = ({
io,
redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis
@@ -173,7 +180,96 @@ const redisSocketEvents = ({
socket.emit("error", { message: "Failed to mark messages as read" });
}
};
- // Mark Messages as Read
+
+ const sendMessage = (data) => {
+ const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid, user } = data;
+ console.dir({ data });
+ logger.log("sms-outbound", "DEBUG", user.email, null, {
+ messagingServiceSid: messagingServiceSid,
+ to: phone(to).phoneNumber,
+ mediaUrl: selectedMedia.map((i) => i.src),
+ text: body,
+ conversationid,
+ isoutbound: true,
+ userid: user.email,
+ image: selectedMedia?.length > 0,
+ image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
+ });
+
+ if (!!to && !!messagingServiceSid && (!!body || !!selectedMedia?.length > 0) && !!conversationid) {
+ twilioClient.messages
+ .create({
+ body: body,
+ messagingServiceSid: messagingServiceSid,
+ to: phone(to).phoneNumber,
+ mediaUrl: selectedMedia.map((i) => i.src)
+ })
+ .then((message) => {
+ let newMessage = {
+ msid: message.sid,
+ text: body,
+ conversationid,
+ isoutbound: true,
+ userid: user.email,
+ image: selectedMedia?.length > 0,
+ image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
+ };
+ gqlClient
+ .request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid })
+ .then((r2) => {
+ //console.log("Responding GQL Message ID", JSON.stringify(r2));
+ logger.log("sms-outbound-success", "DEBUG", user.email, null, {
+ msid: message.sid,
+ conversationid
+ });
+
+ const data = {
+ type: "messaging-outbound",
+ conversationid: newMessage.conversationid || ""
+ };
+
+ // TODO Verify
+ // const messageData = response.insert_messages.returning[0];
+
+ // Broadcast new message to conversation room
+ const room = `conversation-${conversationid}`;
+ io.to(room).emit("new-message", newMessage);
+
+ admin.messaging().send({
+ topic: `${imexshopid}-messaging`,
+ data
+ });
+ })
+ .catch((e2) => {
+ logger.log("sms-outbound-error", "ERROR", user.email, null, {
+ msid: message.sid,
+ conversationid,
+ error: e2
+ });
+ });
+ })
+ .catch((e1) => {
+ logger.log("sms-outbound-error", "ERROR", user.email, null, {
+ conversationid,
+ error: e1
+ });
+ });
+ } else {
+ logger.log("sms-outbound-error", "ERROR", user.email, null, {
+ type: "missing-parameters",
+ messagingServiceSid: messagingServiceSid,
+ to: phone(to).phoneNumber,
+ text: body,
+ conversationid,
+ isoutbound: true,
+ userid: user.email,
+ image: selectedMedia?.length > 0,
+ image_path: selectedMedia?.length > 0 ? selectedMedia.map((i) => i.src) : []
+ });
+ }
+ };
+
+ socket.on("send-message", sendMessage);
socket.on("mark-as-read", markAsRead);
socket.on("join-conversation", joinConversation);
socket.on("open-messaging", openMessaging);