diff --git a/All CSS Files Used in this Project.zip b/All CSS Files Used in this Project.zip new file mode 100644 index 0000000..0f2a7cc Binary files /dev/null and b/All CSS Files Used in this Project.zip differ diff --git a/backend/controllers/message.js b/backend/controllers/message.js index e773eff..5c1e078 100644 --- a/backend/controllers/message.js +++ b/backend/controllers/message.js @@ -8,6 +8,7 @@ const sequelize = require("sequelize"); const { MessageMedia } = require("whatsapp-web.js"); const setMessagesAsRead = async contactId => { + const io = getIO(); try { const result = await Message.update( { read: true }, @@ -26,8 +27,13 @@ const setMessagesAsRead = async contactId => { error.satusCode = 501; throw error; } + + io.to("notification").emit("contact", { + action: "updateUnread", + contactId: contactId, + }); } catch (err) { - next(err); + console.log(err); } }; @@ -38,6 +44,8 @@ exports.getContactMessages = async (req, res, next) => { const { contactId } = req.params; const { searchParam, pageNumber = 1 } = req.query; + console.log(req.body, req.query); + const lowerSerachParam = searchParam.toLowerCase(); const whereCondition = { @@ -59,7 +67,7 @@ exports.getContactMessages = async (req, res, next) => { throw error; } - setMessagesAsRead(contactId); + await setMessagesAsRead(contactId); const messagesFound = await contact.countMessages({ where: whereCondition, @@ -71,7 +79,26 @@ exports.getContactMessages = async (req, res, next) => { order: [["createdAt", "DESC"]], }); - return res.json({ messages: contactMessages.reverse(), messagesFound }); + const serializedMessages = contactMessages.map(message => { + return { + id: message.id, + createdAt: message.createdAt, + updatedAt: message.updatedAt, + messageBody: message.messageBody, + userId: message.userId, + ack: message.ack, + read: message.read, + mediaType: message.mediaType, + contactId: message.contactId, + mediaUrl: `${ + message.mediaUrl + ? `http://localhost:8080/public/${message.mediaUrl}` + : "" + }`, + }; + }); + + return res.json({ messages: serializedMessages.reverse(), messagesFound }); } catch (err) { next(err); } @@ -90,7 +117,7 @@ exports.postCreateContactMessage = async (req, res, next) => { const contact = await Contact.findByPk(contactId); if (media) { const newMedia = MessageMedia.fromFilePath(req.file.path); - message.mediaUrl = req.file.path; + message.mediaUrl = req.file.filename; if (newMedia.mimetype) { message.mediaType = newMedia.mimetype.split("/")[0]; } else { @@ -118,6 +145,7 @@ exports.postCreateContactMessage = async (req, res, next) => { action: "create", message: newMessage, }); + await setMessagesAsRead(contactId); return res.json({ message: "Mensagem enviada" }); } catch (err) { diff --git a/backend/controllers/session.json b/backend/controllers/session.json index aa161d2..3be9c34 100644 --- a/backend/controllers/session.json +++ b/backend/controllers/session.json @@ -1 +1 @@ -{"WABrowserId":"\"W5pw0Llb60mSeV7WOHnk8A==\"","WASecretBundle":"{\"key\":\"alDLbPjonDFzCh5PEPql9cy59LNh1HFG/AZJVoucuYI=\",\"encKey\":\"FQ1MZ2eIH9hKV4dqFoBYTv1/89aopcMAa4CXgh/9csM=\",\"macKey\":\"alDLbPjonDFzCh5PEPql9cy59LNh1HFG/AZJVoucuYI=\"}","WAToken1":"\"fpvmzsdJ0KcZfIl5G4ZTqGxvg474wEfhECz4btE0TDc=\"","WAToken2":"\"1@ueYAfj0uRoSok18uTv8xzCWlvo45N3cefA6tAkUX2iJXP+AQY35sLDxyv1F+q34Ntf9vUcoc4tQG2Q==\""} \ No newline at end of file +{"WABrowserId":"\"E9dnt9Mm/JiFDCMJQHkXBw==\"","WASecretBundle":"{\"key\":\"fHTKtoDcxidboASIs5WV5JKyRrF+SfMktqHXmq/KBJU=\",\"encKey\":\"FrM3OnnEbuEr1JrtKypw5CPSc6rSD5bjbOGstv8ijk4=\",\"macKey\":\"fHTKtoDcxidboASIs5WV5JKyRrF+SfMktqHXmq/KBJU=\"}","WAToken1":"\"Q+xBrzEsm9SAVo3hRyKwC3L/kvNfieq45Jt6/pFePbw=\"","WAToken2":"\"1@WlcfjMFEzc0c9JcHHHAK5BfaDDxigchRdN+GrHmVWsix+b7Od5d9Ls21/JC5ojCqbFBwnzQ5zZ/Fbg==\""} \ No newline at end of file diff --git a/backend/controllers/wbot.js b/backend/controllers/wbot.js index 6009e6a..e06922a 100644 --- a/backend/controllers/wbot.js +++ b/backend/controllers/wbot.js @@ -32,8 +32,19 @@ module.exports = { wbot.on("auth_failure", msg => { console.error("AUTHENTICATION FAILURE", msg); }); - wbot.on("ready", () => { + wbot.on("ready", async () => { console.log("READY"); + // const chats = await wbot.getChats(); // pega as mensagens nao lidas (recebidas quando o bot estava offline) + // let unreadMessages; // todo > salvar isso no DB pra mostrar no frontend + // for (let chat of chats) { + // if (chat.unreadCount > 0) { + // unreadMessages = await chat.fetchMessages({ + // limit: chat.unreadCount, + // }); + // } + // } + + // console.log(unreadMessages); }); return wbot; diff --git a/backend/controllers/wbotMessageListener.js b/backend/controllers/wbotMessageListener.js index 69212fe..d4e65a2 100644 --- a/backend/controllers/wbotMessageListener.js +++ b/backend/controllers/wbotMessageListener.js @@ -61,7 +61,7 @@ const wbotMessageListener = () => { newMessage = await contact.createMessage({ id: msg.id.id, messageBody: msg.body || media.filename, - mediaUrl: path.join("public", media.filename), + mediaUrl: media.filename, mediaType: media.mimetype.split("/")[0], }); } diff --git a/backend/models/Message.js b/backend/models/Message.js index ea6fa34..49f09a9 100644 --- a/backend/models/Message.js +++ b/backend/models/Message.js @@ -7,6 +7,10 @@ const Message = sequelize.define("message", { allowNull: false, primaryKey: true, }, + createdAt: { + allowNull: false, + type: Sequelize.DATE(6), + }, userId: { type: Sequelize.INTEGER, defaultValue: 0 }, ack: { type: Sequelize.INTEGER, defaultValue: 0 }, messageBody: { type: Sequelize.STRING(250), allowNull: false }, diff --git a/backend/package-lock.json b/backend/package-lock.json index 9caefba..0c554f5 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -31,9 +31,9 @@ "dev": true }, "@types/node": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz", - "integrity": "sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==" + "version": "14.0.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.11.tgz", + "integrity": "sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==" }, "@types/yauzl": { "version": "2.9.1", @@ -2103,9 +2103,9 @@ } }, "puppeteer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.1.0.tgz", - "integrity": "sha512-jLa9sqdVx0tPnr2FcwAq+8DSjGhSM4YpkwOf3JE22Ycyqm71SW7B5uGfTyMGFoLCmbCozbLZclCjasPb0flTRw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.3.0.tgz", + "integrity": "sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==", "requires": { "debug": "^4.1.0", "extract-zip": "^2.0.0", @@ -2342,9 +2342,9 @@ "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" }, "sequelize": { - "version": "5.21.11", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.11.tgz", - "integrity": "sha512-ZJw3Hp+NS7iHcTz4fHlKvIBm4I7xYibYRCP4HhSyMB26xgqFYFOXTaeWbHD2UUwAFaksTLw5ntzfpA9kE33SVA==", + "version": "5.21.12", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.12.tgz", + "integrity": "sha512-WRTXLoH72HQnlEUbKI1Iev2yS1/epg72Chz0fHqk0MLn4Wj2pH/TS01lpMDGQqxZPXCxRGrwk1YxwUw2C0yI9A==", "requires": { "bluebird": "^3.5.0", "cls-bluebird": "^2.1.0", @@ -2867,9 +2867,9 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "whatsapp-web.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.6.0.tgz", - "integrity": "sha512-Lr4KG5nA9u18IBGCSVLxwBT1qlJXtLSA83/0efXobQ5j76fVw2I0sIe8KCcMcFnrTr0IIAnnxEqmXcEKnSiaqg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/whatsapp-web.js/-/whatsapp-web.js-1.6.1.tgz", + "integrity": "sha512-pj0JEIuMsWTlqlNr7vRST9XrS+lj/E4i9B7c00FCzUGu6pbPr+95QrYYY56tH5bpCtpsl2NbIB3fbYe3RlxdkA==", "requires": { "@pedroslopez/moduleraid": "^4.1.0", "jsqr": "^1.3.1", diff --git a/backend/package.json b/backend/package.json index 54a9afd..e24d321 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,10 +23,10 @@ "multer": "^1.4.2", "mysql2": "^2.1.0", "qrcode-terminal": "^0.12.0", - "sequelize": "^5.21.11", + "sequelize": "^5.21.12", "sequelize-cli": "^5.5.1", "socket.io": "^2.3.0", - "whatsapp-web.js": "^1.6.0" + "whatsapp-web.js": "^1.6.1" }, "devDependencies": { "nodemon": "^2.0.4" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 594d0e1..7c02bf5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1327,6 +1327,14 @@ "react-transition-group": "^4.4.0" } }, + "@material-ui/icons": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, "@material-ui/styles": { "version": "4.9.14", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.14.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7420d10..c1e4d6d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.9.14", + "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/frontend/src/App.js b/frontend/src/App.js index 99ed515..daa1ee6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -4,6 +4,7 @@ import { toast, ToastContainer } from "react-toastify"; import Home from "./pages/Home/Home"; import Chat from "./pages/Chat/Chat"; +import Chat2 from "./pages/Chat-Material/Chat2"; import Profile from "./pages/Profile/Profile"; import Signup from "./pages/Signup/Signup"; import Login from "./pages/Login/Login"; @@ -52,6 +53,11 @@ const App = () => { path="/chat" render={props => } /> + } + /> ); diff --git a/frontend/src/Images/optimus-prime.jpeg b/frontend/src/Images/optimus-prime.jpeg new file mode 100644 index 0000000..3768e48 Binary files /dev/null and b/frontend/src/Images/optimus-prime.jpeg differ diff --git a/frontend/src/Images/rachel.jpeg b/frontend/src/Images/rachel.jpeg new file mode 100644 index 0000000..089f371 Binary files /dev/null and b/frontend/src/Images/rachel.jpeg differ diff --git a/frontend/src/Images/real-terminator.png b/frontend/src/Images/real-terminator.png new file mode 100644 index 0000000..faed998 Binary files /dev/null and b/frontend/src/Images/real-terminator.png differ diff --git a/frontend/src/Images/rick.jpg b/frontend/src/Images/rick.jpg new file mode 100644 index 0000000..f728ca9 Binary files /dev/null and b/frontend/src/Images/rick.jpg differ diff --git a/frontend/src/Images/robocop.jpg b/frontend/src/Images/robocop.jpg new file mode 100644 index 0000000..7360903 Binary files /dev/null and b/frontend/src/Images/robocop.jpg differ diff --git a/frontend/src/Images/termy.jpg b/frontend/src/Images/termy.jpg new file mode 100644 index 0000000..6c48742 Binary files /dev/null and b/frontend/src/Images/termy.jpg differ diff --git a/frontend/src/components/Navbar/LogedinNavbar.js b/frontend/src/components/Navbar/LogedinNavbar.js index f478769..831450e 100644 --- a/frontend/src/components/Navbar/LogedinNavbar.js +++ b/frontend/src/components/Navbar/LogedinNavbar.js @@ -2,6 +2,7 @@ import React from "react"; import { useHistory } from "react-router-dom"; import { Navbar, Nav, Container } from "react-bootstrap"; import { LinkContainer } from "react-router-bootstrap"; +import "./Navbar.css"; const DefaultNavbar = () => { const username = localStorage.getItem("username"); @@ -31,6 +32,9 @@ const DefaultNavbar = () => { Chat + + Chat MaterialUi + Logado como: {username} diff --git a/frontend/src/components/Navbar/Nabar.css b/frontend/src/components/Navbar/Navbar.css similarity index 100% rename from frontend/src/components/Navbar/Nabar.css rename to frontend/src/components/Navbar/Navbar.css diff --git a/frontend/src/index.js b/frontend/src/index.js index 539dbbd..6191d3a 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,11 +1,15 @@ import React from "react"; import ReactDOM from "react-dom"; -import App from "./App"; import "bootstrap/dist/css/bootstrap.min.css"; +import ScopedCssBaseline from "@material-ui/core/ScopedCssBaseline"; + +import App from "./App"; ReactDOM.render( - + + + , document.getElementById("root") ); diff --git a/frontend/src/pages/Chat-Material/Chat2.js b/frontend/src/pages/Chat-Material/Chat2.js new file mode 100644 index 0000000..2c89d4d --- /dev/null +++ b/frontend/src/pages/Chat-Material/Chat2.js @@ -0,0 +1,57 @@ +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; +import Container from "@material-ui/core/Container"; +import Paper from "@material-ui/core/Paper"; +import { makeStyles } from "@material-ui/core/styles"; + +import ContactsHeader from "./components/ContactsHeader/ContactsHeader"; +import ContactsList from "./components/ContactsList/ContactsList"; +import MessagesList from "./components/MessagesList/MessagesList"; +import MessagesInput from "./components/MessagesInput/MessagesInput"; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + }, + welcomeMsg: { + backgroundColor: "#eee", + // display: "flex", + // flex: 1, + // alignItems: "center", + height: 626, + textAlign: "center", + }, +})); + +const Chat2 = () => { + const classes = useStyles(); + const [selectedContact, setSelectedContact] = useState(null); + + return ( +
+ +

+ + + + + + + {selectedContact ? ( + <> + + + + ) : ( + + Selecione um contato para começar a conversar + + )} + + +
+
+ ); +}; + +export default Chat2; diff --git a/frontend/src/pages/Chat-Material/components/ContactsHeader/ContactsHeader.js b/frontend/src/pages/Chat-Material/components/ContactsHeader/ContactsHeader.js new file mode 100644 index 0000000..9caec03 --- /dev/null +++ b/frontend/src/pages/Chat-Material/components/ContactsHeader/ContactsHeader.js @@ -0,0 +1,48 @@ +import React from "react"; + +import { makeStyles } from "@material-ui/core/styles"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import IconButton from "@material-ui/core/IconButton"; +import MoreVertIcon from "@material-ui/icons/MoreVert"; +import Avatar from "@material-ui/core/Avatar"; + +import profileDefaultPic from "../../../../Images/profile_default.png"; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + }, + contactsHeader: { + display: "flex", + backgroundColor: "#eee", + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + borderTopRightRadius: 0, + }, + settingsIcon: { + alignSelf: "center", + marginLeft: "auto", + padding: 8, + }, +})); + +const ContactsHeader = () => { + const classes = useStyles(); + + const username = localStorage.getItem("username"); + + return ( + + } + title={username} + /> + + + + + ); +}; + +export default ContactsHeader; diff --git a/frontend/src/pages/Chat-Material/components/ContactsList/ContactsList.js b/frontend/src/pages/Chat-Material/components/ContactsList/ContactsList.js new file mode 100644 index 0000000..a1b9a8c --- /dev/null +++ b/frontend/src/pages/Chat-Material/components/ContactsList/ContactsList.js @@ -0,0 +1,209 @@ +import React, { useState, useEffect } from "react"; +import { useHistory } from "react-router-dom"; +import api from "../../../../util/api"; +import openSocket from "socket.io-client"; + +import profileDefaultPic from "../../../../Images/profile_default.png"; + +import { makeStyles } from "@material-ui/core/styles"; +import { green } from "@material-ui/core/colors"; +import Paper from "@material-ui/core/Paper"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemText from "@material-ui/core/ListItemText"; +import ListItemAvatar from "@material-ui/core/ListItemAvatar"; +import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; +import Avatar from "@material-ui/core/Avatar"; +import Divider from "@material-ui/core/Divider"; +import Badge from "@material-ui/core/Badge"; +import SearchIcon from "@material-ui/icons/Search"; +import InputBase from "@material-ui/core/InputBase"; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + }, + + badgeStyle: { + color: "white", + backgroundColor: green[500], + }, + + contactsList: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + height: 500, + overflowY: "scroll", + "&::-webkit-scrollbar": { + width: "8px", + }, + "&::-webkit-scrollbar-thumb": { + // borderRadius: "2px", + boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)", + backgroundColor: "#e8e8e8", + }, + }, + contactsSearchBox: { + background: "#fafafa", + position: "relative", + padding: "10px 13px", + }, + + serachInputWrapper: { + background: "#fff", + borderRadius: 40, + }, + + searchIcon: { + color: "grey", + marginLeft: 7, + marginRight: 7, + verticalAlign: "middle", + }, + + contactsSearchInput: { + border: "none", + borderRadius: 30, + width: "80%", + }, +})); + +const ContactsList = ({ selectedContact, setSelectedContact }) => { + const classes = useStyles(); + const token = localStorage.getItem("token"); + + const [contacts, setContacts] = useState([]); + const [displayedContacts, setDisplayedContacts] = useState([]); + const [notification, setNotification] = useState(true); + + const history = useHistory(); + + useEffect(() => { + const fetchContacts = async () => { + try { + const res = await api.get("/contacts", { + headers: { Authorization: "Bearer " + token }, + }); + setContacts(res.data); + setDisplayedContacts(res.data); + } catch (err) { + if (err.response.data.message === "invalidToken") { + localStorage.removeItem("token"); + localStorage.removeItem("username"); + localStorage.removeItem("userId"); + history.push("/login"); + alert("Sessão expirada, por favor, faça login novamente."); + } + console.log(err); + } + }; + fetchContacts(); + }, [selectedContact, token, notification, history]); + + useEffect(() => { + const socket = openSocket("http://localhost:8080"); + + socket.emit("joinNotification"); + + socket.on("contact", data => { + if (data.action === "create") { + addContact(data.contact); + } + if (data.action === "updateUnread") { + resetUnreadMessages(data.contactId); + } + }); + + socket.on("appMessage", data => { + setNotification(prevState => !prevState); + // handleUnreadMessages(data.message.contactId); + }); + + return () => { + socket.disconnect(); + }; + }, []); + + const resetUnreadMessages = contactId => { + setDisplayedContacts(prevState => { + let aux = [...prevState]; + let contactIndex = aux.findIndex(contact => contact.id === +contactId); + aux[contactIndex].unreadMessages = 0; + + return aux; + }); + }; + + const addContact = contact => { + setContacts(prevState => [contact, ...prevState]); + setDisplayedContacts(prevState => [contact, ...prevState]); + }; + + const handleSelectContact = (e, contact) => { + setSelectedContact(contact); + setNotification(prevState => !prevState); + }; + + const handleSearchContact = e => { + let searchTerm = e.target.value.toLowerCase(); + + setDisplayedContacts( + contacts.filter(contact => + contact.name.toLowerCase().includes(searchTerm) + ) + ); + }; + + return ( + <> + +
+ + +
+
+ + + {displayedContacts.map((contact, index) => ( + + handleSelectContact(e, contact)}> + + + > + + + + + {contact.unreadMessages > 0 && ( + + )} + + + + + ))} + + + + ); +}; + +export default ContactsList; diff --git a/frontend/src/pages/Chat-Material/components/MessagesInput/MessagesInput.js b/frontend/src/pages/Chat-Material/components/MessagesInput/MessagesInput.js new file mode 100644 index 0000000..168a853 --- /dev/null +++ b/frontend/src/pages/Chat-Material/components/MessagesInput/MessagesInput.js @@ -0,0 +1,67 @@ +import React from "react"; + +import { makeStyles } from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import InputBase from "@material-ui/core/InputBase"; + +import AttachFileIcon from "@material-ui/icons/AttachFile"; +import MoodIcon from "@material-ui/icons/Mood"; +import SendIcon from "@material-ui/icons/Send"; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + }, + + newMessageBox: { + background: "#eee", + display: "flex", + padding: "10px 13px", + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + borderBottomLeftRadius: 0, + }, + + messageInputWrapper: { + background: "#fff", + borderRadius: 40, + flex: 1, + }, + + messageInput: { + paddingLeft: 10, + border: "none", + borderRadius: 30, + width: "80%", + }, + + sendMessageIcon: { + opacity: "80%", + margin: 4, + alignSelf: "middle", + cursor: "pointer", + "&:hover": { + opacity: "70%", + }, + }, +})); + +const MessagesInput = () => { + const classes = useStyles(); + + return ( + + + +
+ +
+ +
+ ); +}; + +export default MessagesInput; diff --git a/frontend/src/pages/Chat-Material/components/MessagesList/MessagesList.js b/frontend/src/pages/Chat-Material/components/MessagesList/MessagesList.js new file mode 100644 index 0000000..f7d3edb --- /dev/null +++ b/frontend/src/pages/Chat-Material/components/MessagesList/MessagesList.js @@ -0,0 +1,461 @@ +import React, { useState, useEffect, useRef } from "react"; + +import { makeStyles } from "@material-ui/core/styles"; +import AccessTimeIcon from "@material-ui/icons/AccessTime"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import DoneIcon from "@material-ui/icons/Done"; +import DoneAllIcon from "@material-ui/icons/DoneAll"; +import SearchIcon from "@material-ui/icons/Search"; +import InputBase from "@material-ui/core/InputBase"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import IconButton from "@material-ui/core/IconButton"; +import MoreVertIcon from "@material-ui/icons/MoreVert"; +import Avatar from "@material-ui/core/Avatar"; +import { green } from "@material-ui/core/colors"; + +import api from "../../../../util/api"; +import openSocket from "socket.io-client"; + +import moment from "moment-timezone"; +import InfiniteScrollReverse from "react-infinite-scroll-reverse"; +import ModalImage from "react-modal-image"; +import ReactAudioPlayer from "react-audio-player"; + +const useStyles = makeStyles(theme => ({ + messagesHeader: { + display: "flex", + backgroundColor: "#eee", + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + }, + + messagesSearchInputWrapper: { + margin: 20, + marginLeft: "auto", + background: "#fff", + borderRadius: 40, + width: 250, + }, + + messagesSearchInput: { + border: "none", + borderRadius: 30, + }, + + searchIcon: { + color: "grey", + marginLeft: 7, + marginRight: 7, + verticalAlign: "middle", + }, + + settingsIcon: { + alignSelf: "center", + padding: 8, + }, + + messagesList: { + backgroundImage: 'url("http://localhost:8080/public/wa-background.png")', + display: "flex", + flex: 1, + flexDirection: "column", + padding: "20px 20px 20px 20px", + height: 500, + overflowY: "scroll", + "&::-webkit-scrollbar": { + width: "8px", + }, + "&::-webkit-scrollbar-thumb": { + // borderRadius: "2px", + boxShadow: "inset 0 0 6px rgba(0, 0, 0, 0.3)", + backgroundColor: "#e8e8e8", + }, + }, + wrapper: { + position: "relative", + }, + + circleLoading: { + color: green[500], + position: "absolute", + top: 0, + left: "50%", + marginTop: 12, + // marginLeft: -12, + }, + + messageLeft: { + marginTop: 5, + minWidth: 100, + maxWidth: 600, + height: "auto", + display: "block", + position: "relative", + + backgroundColor: "#ffffff", + alignSelf: "flex-start", + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 8, + paddingLeft: 5, + paddingRight: 5, + paddingTop: 5, + paddingBottom: 0, + boxShadow: "0 2px 2px #808888", + }, + + messageRight: { + marginLeft: 20, + marginTop: 5, + minWidth: 100, + maxWidth: 600, + height: "auto", + display: "block", + position: "relative", + + backgroundColor: "#dcf8c6", + alignSelf: "flex-end", + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + borderBottomLeftRadius: 8, + borderBottomRightRadius: 0, + paddingLeft: 5, + paddingRight: 5, + paddingTop: 5, + paddingBottom: 0, + boxShadow: "0 2px 2px #808888", + }, + + messageMedia: { + objectFit: "cover", + width: 250, + height: 200, + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + borderBottomLeftRadius: 8, + borderBottomRightRadius: 8, + }, + + textContentItem: { + overflowWrap: "break-word", + padding: "0px 60px 10px 0px", + }, + + timestamp: { + fontSize: 11, + position: "absolute", + bottom: 0, + right: 10, + color: "#999", + }, + + dailyTimestamp: { + alignItems: "center", + textAlign: "center", + alignSelf: "center", + width: "110px", + backgroundColor: "#e1f3fb", + margin: "10px", + borderRadius: "10px", + }, + + dailyTimestampText: { + color: "#808888", + padding: 8, + alignSelf: "center", + marginLeft: "0px", + }, +})); + +const MessagesList = ({ selectedContact }) => { + const contactId = selectedContact.id; + const classes = useStyles(); + + const token = localStorage.getItem("token"); + + const [loading, setLoading] = useState(true); + const [messagesList, setMessagesList] = useState([]); + const [hasMore, setHasMore] = useState(false); + const [searchParam, setSearchParam] = useState(""); + const [pageNumber, setPageNumber] = useState(0); + const lastMessageRef = useRef(); + + useEffect(() => { + setMessagesList([]); + }, [searchParam]); + + useEffect(() => { + setLoading(true); + const delayDebounceFn = setTimeout(() => { + console.log(searchParam); + const fetchMessages = async () => { + try { + const res = await api.get("/messages/" + contactId, { + headers: { Authorization: "Bearer " + token }, + params: { searchParam, pageNumber }, + }); + setMessagesList(prevMessages => { + return [...res.data.messages, ...prevMessages]; + }); + setHasMore(res.data.messages.length > 0); + setLoading(false); + if (pageNumber === 1 && res.data.messages.length > 1) { + scrollToBottom(); + } + } catch (err) { + console.log(err); + alert(err); + } + }; + fetchMessages(); + }, 1000); + return () => clearTimeout(delayDebounceFn); + }, [searchParam, pageNumber, contactId, token]); + + useEffect(() => { + const socket = openSocket("http://localhost:8080/"); + + socket.emit("joinChatBox", contactId, () => {}); + + socket.on("appMessage", data => { + if (data.action === "create") { + addMessage(data.message); + scrollToBottom(); + } else if (data.action === "update") { + updateMessageAck(data.message); + } + }); + + return () => { + socket.disconnect(); + setSearchParam(""); + setPageNumber(1); + setMessagesList([]); + }; + }, [contactId]); + + const handleSearch = e => { + setSearchParam(e.target.value); + setPageNumber(1); + }; + + const loadMore = () => { + setPageNumber(prevPageNumber => prevPageNumber + 1); + }; + + const addMessage = message => { + setMessagesList(prevState => { + if (prevState.length >= 20) { + let aux = [...prevState]; + aux.shift(); + aux.push(message); + return aux; + } else { + return [...prevState, message]; + } + }); + }; + + const updateMessageAck = message => { + let id = message.id; + setMessagesList(prevState => { + let aux = [...prevState]; + let messageIndex = aux.findIndex(message => message.id === id); + aux[messageIndex].ack = message.ack; + + return aux; + }); + }; + + const scrollToBottom = () => { + if (lastMessageRef) { + lastMessageRef.current.scrollIntoView({}); + } + }; + + const checkMessaageMedia = message => { + if (message.mediaType === "image") { + return ( + + ); + } + if (message.mediaType === "audio") { + return ; + } + + if (message.mediaType === "video") { + return ( +