From a2ed627f2b42d18feab3ab6f4db52247ee973522 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Mon, 4 Nov 2019 13:21:47 -0500 Subject: [PATCH 01/22] Back-end for getting all the DMs for a user --- functions/handlers/users.js | 118 ++++++++++++++++++++++++++++++++++++ functions/index.js | 6 ++ 2 files changed, 124 insertions(+) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index a3cea0d..01068cf 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -400,6 +400,124 @@ exports.unverifyUser = (req, res) => { return res.status(500).json({error: err.code}); }); } + +// Returns all the DMs that the user is currently participating in +exports.getDirectMessages = (req, res) => { +/* Return value + * data: [DMs] + * dm : { + * dmId: str + * messages: [msgs] + * msg: { + * author: str + * createdAt: ISOString + * message: str + * messageId: str + * } + * recipient: str + * recentMessage: str + * recentMessageTimestamp: ISOString + * } + */ + + // Returns all the messages in a dm documentSnapshot + function getMessages(dm) { + let promise = new Promise((resolve, reject) => { + let messagesCollection = dm.collection('messages'); + + // Check integrity of messages collection + if (messagesCollection === null) return res.status(500).json({error: `DM document ${dm.id} is missing a messages collection`}) + let msgs = []; + let promises = []; + + // Get all of the messages in the DM sorted by when they were created + messagesCollection.orderBy('createdAt', 'desc').get() + .then((dmQuerySnap) => { + dmQuerySnap.forEach((dmQueryDocSnap) => { + promises.push( + dmQueryDocSnap.ref.get() + .then((messageData) => { + msgs.push(messageData.data()); + return + }) + ) + }) + + let waitPromise = Promise.all(promises); + waitPromise.then(() => { + resolve(msgs) + }); + }) + }); + return promise; + } + + + const dms = req.userData.dms; + + // Return null if this user has no DMs + if (dms === null) return res.status(200).json({data: null}); + + let dmsData = []; + let dmPromises = []; + + dms.forEach((dm) => { + let dmData = {}; + // Make a new promise for each DM document + dmPromises.push(new Promise((resolve, reject) => { + dm // DM document reference + .get() + .then((doc) => { + let docData = doc.data(); + + // Recipient is the person you are messaging + docData.authors[0] === req.userData.handle ? + dmData.recipient = docData.authors[1] : + dmData.recipient = docData.authors[0] + + // Get all the messages from this dm document + getMessages(dm) + .then((msgs) => { + dmData.messages = msgs; + dmData.recentMessage = msgs[0] ? msgs[0].message : null; + dmData.recentMessageTimestamp = msgs[0] ? msgs[0].createdAt : null; + dmData.dmId = doc.id; + resolve(dmData); + }) + + + + }).catch((err) => { + console.err(err); + return res.status(400).json({error: { + message: "An error occurred when reading the DM document reference", + error: err + }}); + }) + }).then((dmData) => { + dmsData.push(dmData); + }) + ) + + }) + + // Wait for all DM document promises to resolve before returning data + dmWaitPromise = Promise.all(dmPromises) + .then(() => { + // Sort the DMs so that the ones with the newest messages are at the top + dmsData.sort((a, b) => { + return (b.recentMessageTimestamp < a.recentMessageTimestamp) ? -1 : ((b.recentMessageTimestamp > a.recentMessageTimestamp) ? 1 : 0); + }); + return res.status(200).json({data: dmsData}) + }) + .catch((err) => { + return res.status(500).json({error:{ + message: "An error occurred while sorting", + error: err + }}); + }); +} + exports.getUserHandles = (req, res) => { admin .firestore() diff --git a/functions/index.js b/functions/index.js index f05c7b8..eea2c86 100644 --- a/functions/index.js +++ b/functions/index.js @@ -11,6 +11,7 @@ app.use(cors()); *------------------------------------------------------------------*/ const { getAuthenticatedUser, + getDirectMessages, getUserDetails, getProfileInfo, login, @@ -34,6 +35,9 @@ app.post("/login", login); //Deletes user account app.delete("/delete", fbAuth, deleteUser); +// Returns all direct messages that the user is participating in +app.get("/dm", fbAuth, getDirectMessages); + app.get("/getUser", fbAuth, getUserDetails); // Returns all profile data of the currently logged in user @@ -55,6 +59,7 @@ app.post("/unverifyUser", fbAuth, unverifyUser); // get user handles with search phase app.get("/getUserHandles", fbAuth, getUserHandles); + /*------------------------------------------------------------------* * handlers/post.js * *------------------------------------------------------------------*/ @@ -67,6 +72,7 @@ app.get("/getallPosts", getallPosts); // Adds one post to the database app.post("/putPost", fbAuth, putPost); + /*------------------------------------------------------------------* * handlers/topic.js * *------------------------------------------------------------------*/ From 19d78596b522db6c102c950f379433e0c3411048 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 5 Nov 2019 22:43:29 -0500 Subject: [PATCH 02/22] Check for DMs enabled or disabled --- functions/handlers/users.js | 37 +++++++++++++++++++++++++++++++++++++ functions/index.js | 15 ++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 01068cf..218d31a 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -518,6 +518,43 @@ exports.getDirectMessages = (req, res) => { }); } +// Sends a DM from the caller to the requested DM document +exports.sendDirectMessage = (req, res) => { + return res.status(200).json({message: "Not implemented yet"}) +} + +// Creates a DM between the caller and the user in the request +exports.createDirectMessage = (req, res) => { + return res.status(200).json({message: "Not implemented yet"}) +} + +// Checks if the requested user has DMs enable or not +exports.checkDirectMessagesEnabled = (req, res) => { + username = req.body.user; + if (username === null || username === undefined) return res.status(400).json({error: "No user was sent in the request. The request should have a 'user' key."}); + + db.doc(`/users/${username}`) + .get() + .then((doc) => { + if (doc.exists) { + console.log(doc.data()) + if (doc.data().dmEnabled === true || doc.data().dmEnabled === null || doc.data().dmEnabled === undefined) { + // Assume DMs are enabled if they don't have a dmEnabled key + return res.status(200).json({enabled: true}); + } else { + return res.status(200).json({enabled: false}); + } + } else { + console.log(`${username} is not in the database`); + return res.status(400).json({error: `${username} is not in the database`}); + } + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: err}); + }) +} + exports.getUserHandles = (req, res) => { admin .firestore() diff --git a/functions/index.js b/functions/index.js index eea2c86..ca249d0 100644 --- a/functions/index.js +++ b/functions/index.js @@ -12,6 +12,9 @@ app.use(cors()); const { getAuthenticatedUser, getDirectMessages, + sendDirectMessage, + createDirectMessage, + checkDirectMessagesEnabled, getUserDetails, getProfileInfo, login, @@ -36,7 +39,16 @@ app.post("/login", login); app.delete("/delete", fbAuth, deleteUser); // Returns all direct messages that the user is participating in -app.get("/dm", fbAuth, getDirectMessages); +app.get("/dms", fbAuth, getDirectMessages); + +// Send a message in a DM from one user to another +app.post("/dms/send", fbAuth, sendDirectMessage); + +// Create a new DM between two users +app.post("/dms/new", fbAuth, createDirectMessage); + +// Checks if the user provided has DMs enabled or not +app.get("/dms/enabled", checkDirectMessagesEnabled); app.get("/getUser", fbAuth, getUserDetails); @@ -46,6 +58,7 @@ app.get("/getProfileInfo", fbAuth, getProfileInfo); // Updates the currently logged in user's profile information app.post("/updateProfileInfo", fbAuth, updateProfileInfo); +// Gets all user data for the logged in user app.get("/user", fbAuth, getAuthenticatedUser); // Verifies the user sent to the request From 5560b6e13ce515ade251ec80d02e798eac239927 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 6 Nov 2019 01:01:50 -0500 Subject: [PATCH 03/22] Refactor DM enabled checking to make it re-usable --- functions/handlers/users.js | 86 +++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 218d31a..2b6c2b7 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -518,6 +518,49 @@ exports.getDirectMessages = (req, res) => { }); } +// Returns a promise that resolves if user has DMs enabled +// and rejects if there is an error or DMs are disabled +isDirectMessageEnabled = (username) => { + return new Promise((resolve, reject) => { + let result = {}; + result.code = null; + result.message = null; + if (username === null || username === undefined || username === "") { + result.code = 400; + result.message = "No user was sent in the request. The request should have a non-empty 'user' key."; + reject(result); + } + + db.doc(`/users/${username}`) + .get() + .then((doc) => { + if (doc.exists) { + console.log(doc.data()) + if (doc.data().dmEnabled === true || doc.data().dmEnabled === null || doc.data().dmEnabled === undefined) { + // Assume DMs are enabled if they don't have a dmEnabled key + resolve(result); + } else { + result.code = 0; + reject(result); + } + } else { + console.log(`${username} is not in the database`); + result.code = 400; + result.message = `${username} is not in the database`; + reject(result); + } + }) + .catch((err) => { + console.log("HI") + console.error(err); + result.code = 500; + result.message = err; + reject(result); + }) + }); +} + + // Sends a DM from the caller to the requested DM document exports.sendDirectMessage = (req, res) => { return res.status(200).json({message: "Not implemented yet"}) @@ -525,33 +568,32 @@ exports.sendDirectMessage = (req, res) => { // Creates a DM between the caller and the user in the request exports.createDirectMessage = (req, res) => { - return res.status(200).json({message: "Not implemented yet"}) + // TODO: Check if the user exists + // TODO: Check if this user has dms enabled + // TODO: Check if they have dms enabled + // TODO: Check if there is already a dm + + // this.checkDirectMessagesEnabled(req, res); } // Checks if the requested user has DMs enable or not +/* Request Parameters + * user: str + */ exports.checkDirectMessagesEnabled = (req, res) => { - username = req.body.user; - if (username === null || username === undefined) return res.status(400).json({error: "No user was sent in the request. The request should have a 'user' key."}); - - db.doc(`/users/${username}`) - .get() - .then((doc) => { - if (doc.exists) { - console.log(doc.data()) - if (doc.data().dmEnabled === true || doc.data().dmEnabled === null || doc.data().dmEnabled === undefined) { - // Assume DMs are enabled if they don't have a dmEnabled key - return res.status(200).json({enabled: true}); - } else { - return res.status(200).json({enabled: false}); - } - } else { - console.log(`${username} is not in the database`); - return res.status(400).json({error: `${username} is not in the database`}); - } + isDirectMessageEnabled(req.body.user) + .then(() => { + return res.status(200).json({enabled: true}); }) - .catch((err) => { - console.error(err); - return res.status(500).json({error: err}); + .catch((result) => { + console.log(result); + if (result.code === 0) { + // DMs are disabled + return res.status(200).json({enabled: false}); + } else { + // Some other error occured + return res.status(result.code).json({err: result.message}); + } }) } From 5507247a7f764b141666d53304659ee4e031d684 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 8 Nov 2019 11:42:46 -0500 Subject: [PATCH 04/22] Create a new DM channel between two users --- functions/handlers/users.js | 179 ++++++++++++++++++++++++++++++++++-- 1 file changed, 173 insertions(+), 6 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 2b6c2b7..5a6fc4a 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -535,12 +535,13 @@ isDirectMessageEnabled = (username) => { .get() .then((doc) => { if (doc.exists) { - console.log(doc.data()) + // console.log(doc.data()) if (doc.data().dmEnabled === true || doc.data().dmEnabled === null || doc.data().dmEnabled === undefined) { // Assume DMs are enabled if they don't have a dmEnabled key resolve(result); } else { result.code = 0; + result.message = `${username} has DMs disabled`; reject(result); } } else { @@ -560,6 +561,114 @@ isDirectMessageEnabled = (username) => { }); } +// Returns a promise that resolves if the data in the DM is valid and +// rejects if there are any error. Errors are returned in the promise +verifyDirectMessageIntegrity = (dmRef) => { + return new Promise((resolve, reject) => { + resolve("Not implemented yet"); + }) +} + +// Returns a promise that resolves if there is not already a DM channel +// between the creator and recipient usernames. It rejects if one already +// exists or there is an error. +checkNoDirectMessageExists = (creator, recipient) => { + + // Checks if there are any DM channels open with userB on userA's side + oneWayCheck = (userA, userB) => { + return new Promise((resolve, reject) => { + db.doc(`/users/${userA}`) + .get() + .then((userASnapshot) => { + const dmList = userASnapshot.data().dms; + if (dmList === null || dmList === undefined) { + // They don't have any DMs yet + userASnapshot.ref.set({dms:[]}, {merge: true}) + .then(() => { + resolve(); + }) + } else if (dmList.length === 0) { + // Their DMs are empty + resolve(); + } else { + let dmDocs = []; + let forEachPromises = []; + dmList.forEach((dmRef) => { + forEachPromises.push( + dmRef.get() + .then((dmDoc) => { + // TODO: Figure out why dmDoc.exists() isn't working + // Make sure all of the docs exist and none of the references + // are broken + // if (dmDoc.exists()) { + dmDocs.push(dmDoc); + // } else { + // console.log(`DM reference /dm/${dmDoc.id} is invalid`); + // reject(`DM reference /dm/${dmDoc.id} is invalid`); + // } + }) + ) + }) + Promise.all(forEachPromises) + .then(() => { + // Check if any of the DMs have for userA have userA and userB as the authors. + // This would mean that they already have a DM channel + dmDocs.forEach((dmDoc) => { + // Checking if any of the authors key in any of their DMs are missing + const authors = dmDoc.data().authors; + if (authors === null || authors === undefined || authors.length !== 2) { + console.log(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + reject(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + } + + if ( + (authors[0] === userA && authors[1] === userB) || + (authors[1] === userA && authors[0] === userB) + ) { + console.log(`${userA} already has a DM channel between ${userA} and ${userB}`); + reject(`${userA} already has a DM channel between ${userA} and ${userB}`); + } else { + resolve(); + } + }) + }) + } + }) + }) + + } + + return new Promise((resolve, reject) => { + let creatorPromise = oneWayCheck(creator, recipient); + let recipientPromise = oneWayCheck(recipient, creator); + + Promise.all([creatorPromise, recipientPromise]) + .then(() => { + resolve(); + }) + .catch((err) => { + console.log(err) + reject(err); + }) + }) +} + +addDirectMessageToUser = (username, dmRef) => { + return new Promise((resolve, reject) => { + db.doc(`/users/${username}`).get() + .then((docSnap) => { + let dmList = docSnap.data().dms; + dmList.push(dmRef) + return db.doc(`/users/${username}`).update({dms: dmList}); + }) + .then(() => { + resolve(); + }) + .catch((err) => { + reject(err); + }) + }) +} // Sends a DM from the caller to the requested DM document exports.sendDirectMessage = (req, res) => { @@ -567,13 +676,71 @@ exports.sendDirectMessage = (req, res) => { } // Creates a DM between the caller and the user in the request +/* Request Parameters + * user: str + */ exports.createDirectMessage = (req, res) => { - // TODO: Check if the user exists - // TODO: Check if this user has dms enabled - // TODO: Check if they have dms enabled - // TODO: Check if there is already a dm + const creator = req.userData.handle; + const recipient = req.body.user; - // this.checkDirectMessagesEnabled(req, res); + // Check if they are DMing themselves + if (creator === recipient) return res.status(400).json({error: "A user cannot DM themselves"}); + + // Check if this user has DMs enabled + let creatorEnabled = isDirectMessageEnabled(creator); + + // Check if the requested user has DMs enabled + let recipientEnabled = isDirectMessageEnabled(recipient); + + // Make sure that they don't already have a DM channel + let noDMExists = checkNoDirectMessageExists(creator, recipient) + + + let dataValidations = [ + creatorEnabled, + recipientEnabled, + noDMExists + ] + + Promise.all(dataValidations) + .then(() => { + // Create a new DM document + return db.collection("dm").add({}) + }) + .then((dmDocRef) => { + // Fill it with some data. + // Note that there isn't a messages collection by default. + let dmData = { + dmId: dmDocRef.id, + authors: [creator, recipient] + } + + // Update DM document + let dmDocPromise = dmDocRef.set(dmData); + + // Add the DM reference to the creator + let updateCreatorPromise = addDirectMessageToUser(creator, dmDocRef); + + // Add the DM reference to the recipient + let updateRecipientPromise = addDirectMessageToUser(recipient, dmDocRef); + + // Wait for all promises + return Promise.all([dmDocPromise, updateCreatorPromise, updateRecipientPromise]); + }) + .then (() => { + return res.status(201).json({message: "Success!"}); + }) + .catch((err) => { + console.log(err); + + if (err.code && err.message && err.code > 0) { + // Specific error that I've created + return res.status(err.code).json({err: err.message}); + } else { + // Generic or firebase error + return res.status(500).json({err}); + } + }) } // Checks if the requested user has DMs enable or not From 02197ee940c0cc840858fe7124ed33fddd423738 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 8 Nov 2019 17:54:56 -0500 Subject: [PATCH 05/22] Sending Direct Messages --- functions/handlers/users.js | 88 ++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 5a6fc4a..632ca5e 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -425,27 +425,33 @@ exports.getDirectMessages = (req, res) => { let promise = new Promise((resolve, reject) => { let messagesCollection = dm.collection('messages'); - // Check integrity of messages collection - if (messagesCollection === null) return res.status(500).json({error: `DM document ${dm.id} is missing a messages collection`}) + // If the messagesCollection is missing, that mean that there aren't any messages + if (messagesCollection === null || messagesCollection === undefined) { + return; + } + let msgs = []; let promises = []; // Get all of the messages in the DM sorted by when they were created - messagesCollection.orderBy('createdAt', 'desc').get() + messagesCollection.get() .then((dmQuerySnap) => { dmQuerySnap.forEach((dmQueryDocSnap) => { promises.push( dmQueryDocSnap.ref.get() .then((messageData) => { msgs.push(messageData.data()); - return + return; }) ) }) let waitPromise = Promise.all(promises); waitPromise.then(() => { - resolve(msgs) + msgs.sort((a, b) => { + return (b.createdAt < a.createdAt) ? -1 : ((b.createdAt > a.createdAt) ? 1 : 0); + }) + resolve(msgs); }); }) }); @@ -456,7 +462,7 @@ exports.getDirectMessages = (req, res) => { const dms = req.userData.dms; // Return null if this user has no DMs - if (dms === null) return res.status(200).json({data: null}); + if (dms === null || dms.length === 0) return res.status(200).json({data: null}); let dmsData = []; let dmPromises = []; @@ -671,8 +677,76 @@ addDirectMessageToUser = (username, dmRef) => { } // Sends a DM from the caller to the requested DM document +/* Request Parameters + * message: str + * user: str + */ exports.sendDirectMessage = (req, res) => { - return res.status(200).json({message: "Not implemented yet"}) + // TODO: add error checking for if message or user is null + const creator = req.userData.handle; + const recipient = req.body.user; + const message = req.body.message; + + const newMessage = { + author: creator, + createdAt: new Date().toISOString(), + message, + messageId: null + } + + db.doc(`/users/${creator}`).get() + .then((userDoc) => { + let dmList = userDoc.data().dms; + let dmRefPromises = []; + dmList.forEach((dmRef) => { + dmRefPromises.push( + new Promise((resolve, reject) => { + dmRef.get() + .then((dmDoc) => { + let authors = dmDoc.data().authors; + if ( + (authors[0] === creator && authors[1] === recipient) || + (authors[1] === creator && authors[0] === recipient) + ) { + resolve({correct: true, dmRef}); + } else { + resolve({correct: false, dmRef}); + } + }) + .catch((err) => { + reject(err); + }) + }) + ) + }) + + return Promise.all(dmRefPromises); + }) + .then((results) => { + let correctDMRef = null; + results.forEach((result) => { + if (result.correct) { + correctDMRef = result.dmRef; + } + }) + + if (correctDMRef === null) { + console.log(`There is no DM channel between ${creator} and ${recipient}.`); + return res.status(400).json({error: `There is no DM channel between ${creator} and ${recipient}.`}); + } + + return db.collection(`/dm/${correctDMRef.id}/messages`).add(newMessage); + }) + .then((newMsgRef) => { + return newMsgRef.update({messageId: newMsgRef.id}, {merge: true}); + }) + .then(() => { + return res.status(200).json({message: "OK"}); + }) + .catch((err) => { + console.log(err); + return res.status(500).json({error: err}); + }) } // Creates a DM between the caller and the user in the request From 19f8f70bf2c7de3c918673565d9c0ef6f23ba7be Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 8 Nov 2019 18:48:00 -0500 Subject: [PATCH 06/22] Fixing DMList errors --- functions/handlers/users.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 632ca5e..74893d4 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -462,7 +462,7 @@ exports.getDirectMessages = (req, res) => { const dms = req.userData.dms; // Return null if this user has no DMs - if (dms === null || dms.length === 0) return res.status(200).json({data: null}); + if (dms === undefined || dms === null || dms.length === 0) return res.status(200).json({data: null}); let dmsData = []; let dmPromises = []; @@ -697,6 +697,10 @@ exports.sendDirectMessage = (req, res) => { db.doc(`/users/${creator}`).get() .then((userDoc) => { let dmList = userDoc.data().dms; + + // Return if the creator doesn't have any DMs. + // This means they have not created a DM's channel yet + if (dmList === null || dmList === undefined) return res.status(400).json({error: `There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.`}) let dmRefPromises = []; dmList.forEach((dmRef) => { dmRefPromises.push( @@ -731,8 +735,8 @@ exports.sendDirectMessage = (req, res) => { }) if (correctDMRef === null) { - console.log(`There is no DM channel between ${creator} and ${recipient}.`); - return res.status(400).json({error: `There is no DM channel between ${creator} and ${recipient}.`}); + console.log(`There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.`); + return res.status(400).json({error: `There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.`}); } return db.collection(`/dm/${correctDMRef.id}/messages`).add(newMessage); From 2f5ec1c5ac194fe7b6eebbea3981e7fd8b2a2a63 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 12 Nov 2019 23:50:53 -0500 Subject: [PATCH 07/22] Basic style for direct messages --- twistter-frontend/src/pages/directMessages.js | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 twistter-frontend/src/pages/directMessages.js diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js new file mode 100644 index 0000000..b3c9ab2 --- /dev/null +++ b/twistter-frontend/src/pages/directMessages.js @@ -0,0 +1,254 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types'; + +// Material UI +import Grid from '@material-ui/core/Grid' +import TextField from '@material-ui/core/TextField'; +import Typography from '@material-ui/core/Typography'; +import withStyles from "@material-ui/core/styles/withStyles"; +import GridList from "@material-ui/core/GridList"; +import GridListTile from '@material-ui/core/GridListTileBar'; +import Card from "@material-ui/core/Card"; + +import { connect } from 'react-redux'; +import { Box } from '@material-ui/core'; + +const styles = { + pageContainer: { + // height: "100%" + minHeight: "calc(100vh - 50px - 60px)" + }, + sidePadding: { + maxWidth: 350, + // margin: "auto" + }, + dmList: { + width: 300, + marginLeft: 15, + // borderRight: "solid" + }, + dmItem: { + marginBottom: 1 + // height: 100, + // border: "solid grey 1px", + // boxShadow: "2px 0px grey" + }, + dmItemUsernameSelected: { + // marginLeft: "auto" + // float: "left" + fontSize: 20, + color: "white" + }, + dmItemUsernameUnselected: { + fontSize: 20, + color: "#1da1f2" + }, + dmRecentMessageSelected: { + color: "#D6D6D6" + }, + dmRecentMessageUnselected: { + color: "black" + }, + dmListItemContainer: { + height: 100 + }, + dmCardUnselected: { + fontSize: 20, + backgroundColor: "#FFFFFF" + }, + dmCardSelected: { + fontSize: 20, + backgroundColor: "#1da1f2" + }, + messagesGrid: { + // // margin: "auto" + // height: "auto", + // width: "auto" + }, + messagesContainer: { + height: "100%", + // width: "calc(100% - 4)", + width: 450, + marginLeft: 2, + marginRight: 17 + }, + fromMessage: { + minWidth: 150, + maxWidth: 350, + minHeight: 40, + marginRight: 2, + marginTop: 2, + marginBottom: 10, + backgroundColor: "#008394", + color: "#FFFFFF", + float: "right" + }, + toMessage : { + minWidth: 150, + maxWidth: 350, + minHeight: 40, + marginLeft: 2, + marginTop: 2, + marginBottom: 10, + backgroundColor: "#008394", + color: "#FFFFFF", + float: "left" + }, + messageContent: { + textAlign: "left", + marginLeft: 5, + marginRight: 5 + }, + messageTime: { + color: "#D6D6D6", + textAlign: "left", + marginLeft: 5, + fontSize: 12 + } +} + + +export class directMessages extends Component { + constructor() { + super(); + this.state = { + selectedMessage: "-1" + }; + } + + + + showAlert = (event) => { + // alert(event.target.dataset) + let paper; + if (event.target.parentNode.dataset.key === undefined) { + paper = event.target.parentNode.parentNode.dataset; + } else { + paper = event.target.parentNode.dataset; + } + this.setState({ + selectedMessage: paper.key + }) + } + + render() { + const { classes } = this.props; + + const selected = true; + + let dmListMarkup = ( + + + {this.state.selectedMessage === "0" ? + + + keanureeves + This is the most recent message + + + : + + + keanureeves + This is the most recent message + + } + + + {this.state.selectedMessage === "1" ? + + + keanureeves + This is the most recent message + + + : + + + keanureeves + This is the most recent message + + } + + + {this.state.selectedMessage === "2" ? + + + keanureeves + This is the most recent message + + + : + + + keanureeves + This is the most recent message + + } + + + + ) + + let messagesMarkup = ( + + + + hello + Tues 3:26pm + + + + + Hey, what's up? + Tues 3:26pm + + + + + not much. just chillin' + Tues 3:27pm + + + + + yayayay yay ayay ya ys ydyyasydasy yd yas dyas ydyasy dyasydy asyd yay ydysyd yaysdy yasy dyas ydysyd yasy dasy dyaysydya sydyasyd yasyd yasyd yasydyasdy yas ys ysdyydayaysyd ysdyasyd ysdyy yasyd asydyasy dyasydy + Tues 9:35pm + + + + ) + + return ( + + + + {dmListMarkup} + + + + {selected && + + {messagesMarkup} + } + {!selected && + Select a DM on the left} + + + + + + ) + } +} + +directMessages.propTypes = { + classes: PropTypes.object.isRequired +} + +const mapStateToProps = (state) => ({ + user: state.user, +}); + +// export default directMessages +export default connect(mapStateToProps)(withStyles(styles)(directMessages)); From 63e917f54c0459bcb8bdf9454e25052fce819ef0 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 15 Nov 2019 15:20:39 -0500 Subject: [PATCH 08/22] Fixed the sorting of messages --- functions/handlers/users.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 74893d4..0b1fb69 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -433,7 +433,7 @@ exports.getDirectMessages = (req, res) => { let msgs = []; let promises = []; - // Get all of the messages in the DM sorted by when they were created + // Get all of the messages in the DM messagesCollection.get() .then((dmQuerySnap) => { dmQuerySnap.forEach((dmQueryDocSnap) => { @@ -448,8 +448,10 @@ exports.getDirectMessages = (req, res) => { let waitPromise = Promise.all(promises); waitPromise.then(() => { + // Sort the messages in reverse order by date + // Newest should be at the bottom, because that's how they will be displayed on the front-end msgs.sort((a, b) => { - return (b.createdAt < a.createdAt) ? -1 : ((b.createdAt > a.createdAt) ? 1 : 0); + return (b.createdAt > a.createdAt) ? -1 : ((b.createdAt < a.createdAt) ? 1 : 0); }) resolve(msgs); }); @@ -485,8 +487,8 @@ exports.getDirectMessages = (req, res) => { getMessages(dm) .then((msgs) => { dmData.messages = msgs; - dmData.recentMessage = msgs[0] ? msgs[0].message : null; - dmData.recentMessageTimestamp = msgs[0] ? msgs[0].createdAt : null; + dmData.recentMessage = msgs.length !== 0 ? msgs[msgs.length - 1].message : null; + dmData.recentMessageTimestamp = msgs.length !== 0 ? msgs[msgs.length - 1].createdAt : null; dmData.dmId = doc.id; resolve(dmData); }) From 0f1cfe03935561388f37c3fadb1e1c470ab98796 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 19 Nov 2019 01:18:20 -0500 Subject: [PATCH 09/22] Front-end layout mostly completed --- twistter-frontend/src/pages/directMessages.js | 791 +++++++++++++----- 1 file changed, 563 insertions(+), 228 deletions(-) diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index b3c9ab2..ce05a43 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -1,254 +1,589 @@ -import React, { Component } from 'react' +import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; // Material UI -import Grid from '@material-ui/core/Grid' +import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; +import Card from '@material-ui/core/Card'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import Fab from '@material-ui/core/Fab'; +import Grid from '@material-ui/core/Grid'; +import Popover from '@material-ui/core/Popover'; import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; -import withStyles from "@material-ui/core/styles/withStyles"; -import GridList from "@material-ui/core/GridList"; -import GridListTile from '@material-ui/core/GridListTileBar'; -import Card from "@material-ui/core/Card"; +import withStyles from '@material-ui/core/styles/withStyles'; +// Material UI Icons +import AddCircleIcon from '@material-ui/icons/AddBox'; +import CheckMarkIcon from '@material-ui/icons/Check'; +import ErrorIcon from '@material-ui/icons/ErrorOutline'; +import SendIcon from '@material-ui/icons/Send'; + +// Redux import { connect } from 'react-redux'; -import { Box } from '@material-ui/core'; +import { getDirectMessages } from '../redux/actions/dataActions'; const styles = { - pageContainer: { - // height: "100%" - minHeight: "calc(100vh - 50px - 60px)" - }, - sidePadding: { - maxWidth: 350, - // margin: "auto" - }, - dmList: { - width: 300, - marginLeft: 15, - // borderRight: "solid" - }, - dmItem: { - marginBottom: 1 - // height: 100, - // border: "solid grey 1px", - // boxShadow: "2px 0px grey" - }, - dmItemUsernameSelected: { - // marginLeft: "auto" - // float: "left" - fontSize: 20, - color: "white" - }, - dmItemUsernameUnselected: { - fontSize: 20, - color: "#1da1f2" - }, - dmRecentMessageSelected: { - color: "#D6D6D6" - }, - dmRecentMessageUnselected: { - color: "black" - }, - dmListItemContainer: { - height: 100 - }, - dmCardUnselected: { - fontSize: 20, - backgroundColor: "#FFFFFF" - }, - dmCardSelected: { - fontSize: 20, - backgroundColor: "#1da1f2" - }, - messagesGrid: { - // // margin: "auto" - // height: "auto", - // width: "auto" - }, - messagesContainer: { - height: "100%", - // width: "calc(100% - 4)", - width: 450, - marginLeft: 2, - marginRight: 17 - }, - fromMessage: { - minWidth: 150, - maxWidth: 350, - minHeight: 40, - marginRight: 2, - marginTop: 2, - marginBottom: 10, - backgroundColor: "#008394", - color: "#FFFFFF", - float: "right" - }, - toMessage : { - minWidth: 150, - maxWidth: 350, - minHeight: 40, - marginLeft: 2, - marginTop: 2, - marginBottom: 10, - backgroundColor: "#008394", - color: "#FFFFFF", - float: "left" - }, - messageContent: { - textAlign: "left", - marginLeft: 5, - marginRight: 5 - }, - messageTime: { - color: "#D6D6D6", - textAlign: "left", - marginLeft: 5, - fontSize: 12 - } -} - + pageContainer: { + minHeight: 'calc(100vh - 50px - 60px)' + }, + sidePadding: { + maxWidth: 350 + }, + dmList: { + width: 300, + marginLeft: 15 + }, + dmItem: { + marginBottom: 1 + }, + dmItemUsernameSelected: { + fontSize: 20, + color: 'white' + }, + dmItemUsernameUnselected: { + fontSize: 20, + color: '#1da1f2' + }, + dmItemTimeSelected: { + color: '#D6D6D6', + fontSize: 12, + float: 'right', + marginRight: 5, + marginTop: 5 + }, + dmItemTimeUnselected: { + color: 'black', + fontSize: 12, + float: 'right', + marginRight: 5, + marginTop: 5 + }, + dmRecentMessageSelected: { + color: '#D6D6D6' + }, + dmRecentMessageUnselected: { + color: 'black' + }, + dmListItemContainer: { + height: 100 + }, + dmListTextLayout: { + height: '100%' + }, + dmCardUnselected: { + fontSize: 20, + backgroundColor: '#FFFFFF' + }, + dmCardSelected: { + fontSize: 20, + backgroundColor: '#1da1f2' + }, + messagesGrid: { + // // margin: "auto" + // height: "auto", + // width: "auto" + }, + messagesBox: { + width: 450 + }, + messagesContainer: { + height: 'calc(100vh - 50px - 110px)', + overflow: 'auto', + width: 450, + marginLeft: 2, + marginRight: 17 + }, + fromMessage: { + minWidth: 150, + maxWidth: 350, + minHeight: 40, + marginRight: 2, + marginTop: 2, + marginBottom: 10, + backgroundColor: '#008394', + color: '#FFFFFF', + float: 'right' + }, + toMessage: { + minWidth: 150, + maxWidth: 350, + minHeight: 40, + marginLeft: 2, + marginTop: 2, + marginBottom: 10, + backgroundColor: '#008394', + color: '#FFFFFF', + float: 'left' + }, + messageContent: { + textAlign: 'left', + marginLeft: 5, + marginRight: 5 + }, + messageTime: { + color: '#D6D6D6', + textAlign: 'left', + marginLeft: 5, + fontSize: 12 + }, + writeMessage: { + backgroundColor: '#FFFFFF', + boxShadow: '0px 0px 5px 0px grey', + width: 450 + }, + messageTextField: { + width: 388 + }, + messageButton: { + backgroundColor: '#1da1f2', + marginTop: 8, + marginLeft: 2 + }, + loadingUsernameCheck: { + height: 85, + width: 85, + marginLeft: 5 + }, + errorIcon: { + height: 55, + width: 55, + marginLeft: 5, + color: '#ff3d00' + }, + checkMarkIcon: { + height: 55, + width: 55, + marginLeft: 5, + color: '#1da1f2' + } +}; export class directMessages extends Component { - constructor() { - super(); - this.state = { - selectedMessage: "-1" - }; - } + constructor() { + super(); + this.state = { + hasChannelSelected: false, + selectedChannel: null, + dmData: null, + anchorEl: null, + createDMUsername: '', + checkingUsername: false, + usernameValid: false + }; + } + componentDidUpdate() { + if (this.state.hasChannelSelected) { + document.getElementById('messagesContainer').scrollTop = document.getElementById( + 'messagesContainer' + ).scrollHeight; + } + } + componentDidMount() { + // this.props.getDirectMessages(); + const resp = { + data: [ + { + recipient: 'batman', + messages: [], + recentMessage: null, + recentMessageTimestamp: null, + dmId: 'Lifb0XAONpNLJRhDnOHj' + }, + { + recipient: 'CrazyEddy', + messages: [ + { + messageId: 'yGqcBbDSM8TsoaQAfAVc', + author: 'CrazyEddy', + message: 'This is message 1', + createdAt: '2019-11-04T17:10:29.180Z' + }, + { + messageId: 'c1Bd1REkMBaMaraH10WP', + author: 'keanureeves', + message: 'This is message 2', + createdAt: '2019-11-04T17:33:35.169Z' + }, + { + messageId: 'CL5sThnuekks6579MKuF', + author: 'keanureeves', + message: 'Yo, this my first message', + createdAt: '2019-11-08T22:15:08.456Z' + }, + { + messageId: 'BgMSSlLLLdC1DMtJl5VJ', + author: 'CrazyEddy', + message: 'That is epic', + createdAt: '2019-11-08T22:20:16.768Z' + }, + { + message: 'test test test', + createdAt: '2019-11-08T22:20:58.961Z', + messageId: '9AeUuz0l4wWyQSG6RQ4A', + author: 'keanureeves' + }, + { + messageId: 'zhfEpirBK7jl9FnFMtsQ', + author: 'CrazyEddy', + message: 'noice', + createdAt: '2019-11-08T22:21:29.768Z' + }, + { + message: 'What time is it?', + createdAt: '2019-11-08T22:23:27.353Z', + messageId: 'XwyKwFU2L5wrTKedzlyF', + author: 'CrazyEddy' + }, + { + message: "it's 5:24 right now", + createdAt: '2019-11-08T22:24:21.807Z', + messageId: 'qeasHYkAtTGvjnc3VJAi', + author: 'keanureeves' + }, + { + messageId: 'I7kzyLUd9Pp5qzxTPzQv', + author: 'keanureeves', + message: 'a', + createdAt: '2019-11-08T22:31:42.852Z' + }, + { + messageId: 'iySWBDFFrbY8FT6E61NL', + author: 'keanureeves', + message: 'b', + createdAt: '2019-11-08T22:31:51.558Z' + }, + { + messageId: 'Yis0vXSEuMggj6z5Mq1a', + author: 'keanureeves', + message: 'c', + createdAt: '2019-11-08T22:32:01.293Z' + }, + { + messageId: 'DIgjDvFczqO0OWkOrL0t', + author: 'keanureeves', + message: 'd', + createdAt: '2019-11-08T22:32:16.095Z' + }, + { + message: 'e', + createdAt: '2019-11-08T22:32:22.134Z', + messageId: '6h1dnFE440MOrjySEQHU', + author: 'keanureeves' + }, + { + messageId: 'lmOGGYUWZyB3xG38T7lG', + author: 'keanureeves', + message: 'f', + createdAt: '2019-11-08T22:32:28.424Z' + }, + { + message: 'g', + createdAt: '2019-11-08T22:32:37.632Z', + messageId: '64swTj7yiFy7SF6BPbki', + author: 'keanureeves' + } + ], + recentMessage: 'g', + recentMessageTimestamp: '2019-11-08T22:32:37.632Z', + dmId: 'avGcIs4PFCJhc4EDqAfe' + } + ] + }; + // const resp = {"data": null} + this.setState({ dmData: resp.data }); + } - showAlert = (event) => { - // alert(event.target.dataset) - let paper; - if (event.target.parentNode.dataset.key === undefined) { - paper = event.target.parentNode.parentNode.dataset; - } else { - paper = event.target.parentNode.dataset; - } - this.setState({ - selectedMessage: paper.key - }) - } + // Handles selecting different DM channels + handleClickChannel = (event) => { + this.setState({ + hasChannelSelected: true + }); + let dmChannelKey; - render() { - const { classes } = this.props; + // Determine which DM channel was clicked by finding the key + // An if statement is needed because the user could click the card or the typography + if (event.target.parentNode.parentNode.parentNode.dataset.key === undefined) { + // They clicked text + dmChannelKey = event.target.parentNode.parentNode.parentNode.parentNode.dataset.key; + } else { + // They clicked the background/card + dmChannelKey = event.target.parentNode.parentNode.parentNode.dataset.key; + } - const selected = true; + // Save the entire DM channel in the state so that it is easier to load the messages + this.state.dmData.forEach((channel) => { + if (channel.dmId === dmChannelKey) { + this.setState({ + selectedChannel: channel + }); + } + }); + }; - let dmListMarkup = ( - - - {this.state.selectedMessage === "0" ? - - - keanureeves - This is the most recent message - - - : - - - keanureeves - This is the most recent message - - } - - - {this.state.selectedMessage === "1" ? - - - keanureeves - This is the most recent message - - - : - - - keanureeves - This is the most recent message - - } - - - {this.state.selectedMessage === "2" ? - - - keanureeves - This is the most recent message - - - : - - - keanureeves - This is the most recent message - - } - - - - ) + formatDateToString(dateString) { + let newDate = new Date(Date.parse(dateString)); + return newDate.toDateString(); + } - let messagesMarkup = ( - - - - hello - Tues 3:26pm - - - - - Hey, what's up? - Tues 3:26pm - - - - - not much. just chillin' - Tues 3:27pm - - - - - yayayay yay ayay ya ys ydyyasydasy yd yas dyas ydyasy dyasydy asyd yay ydysyd yaysdy yasy dyas ydysyd yasy dasy dyaysydya sydyasyd yasyd yasyd yasydyasdy yas ys ysdyydayaysyd ysdyasyd ysdyy yasyd asydyasy dyasydy - Tues 9:35pm - - - - ) + formatDateToTimeDiff(dateString) { + return dayjs(dateString).fromNow(); + } - return ( - - - - {dmListMarkup} - - - - {selected && - - {messagesMarkup} - } - {!selected && - Select a DM on the left} - - - - - - ) - } + handleOpenAddDMPopover = (event) => { + this.setState({ + anchorEl: event.currentTarget + }); + }; + + handleCloseAddDMPopover = () => { + this.setState({ + anchorEl: null, + createDMUsername: '', + checkingUsername: false, + usernameValid: false + }); + }; + + handleChangeAddDMUsername = (event) => { + this.setState({ + checkingUsername: true, + createDMUsername: event.target.value + }); + }; + + render() { + const { classes } = this.props; + dayjs.extend(relativeTime); + + // Used for the add button on the dmList + const open = Boolean(this.state.anchorEl); + const id = open ? 'simple-popover' : undefined; + + let dmListMarkup = this.state.dmData ? ( + this.state.dmData.map((channel) => ( + + + + + + + {channel.recipient} + + + {channel.recentMessage ? channel.recentMessage : 'No messages'} + + + + + {channel.recentMessageTimestamp ? ( + this.formatDateToTimeDiff(channel.recentMessageTimestamp) + ) : null} + + + + + + )) + ) : ( +

You don't have any DMs yet

+ ); + + let messagesMarkup = + this.state.selectedChannel !== null ? this.state.selectedChannel.messages.length > 0 ? ( + this.state.selectedChannel.messages.map((messageObj) => ( + + + {messageObj.message} + + {this.formatDateToString(messageObj.createdAt)} + + + + )) + ) : ( +

No DMs here

+ ) : ( +

Select a DM channel

+ ); + + let addDMMarkup = ( +
+ + + + + + + + + + Who would you like to start a DM with? + + + + + {this.state.checkingUsername && ( + + ) // Won't accept classes style for some reason + } + {!this.state.usernameValid && + !this.state.checkingUsername && } + {this.state.usernameValid && + !this.state.checkingUsername && ( + + )} + + + + + + + +
+ ); + + return ( + + + + + + {dmListMarkup} + + {addDMMarkup} + + + + + + + {this.state.hasChannelSelected && ( + + + + {messagesMarkup} + + + + + + + + + + )} + {!this.state.hasChannelSelected && + this.state.dmData && Select a DM on the left} + + + + + ); + } } directMessages.propTypes = { - classes: PropTypes.object.isRequired -} + classes: PropTypes.object.isRequired, + getDirectMessages: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + UI: PropTypes.object.isRequired +}; const mapStateToProps = (state) => ({ - user: state.user, + user: state.user, + UI: state.UI, + directMessages: state.directMessages }); -// export default directMessages -export default connect(mapStateToProps)(withStyles(styles)(directMessages)); +const mapActionsToProps = { + getDirectMessages +}; + +export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(directMessages)); From a59c2feb4a2bd6ba532a87aee23077ec08e1f78d Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 19 Nov 2019 01:30:00 -0500 Subject: [PATCH 10/22] Fix sorting so empty DM channels go to the bottom of the list --- functions/handlers/users.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 0b1fb69..04dfa0b 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -514,7 +514,19 @@ exports.getDirectMessages = (req, res) => { .then(() => { // Sort the DMs so that the ones with the newest messages are at the top dmsData.sort((a, b) => { - return (b.recentMessageTimestamp < a.recentMessageTimestamp) ? -1 : ((b.recentMessageTimestamp > a.recentMessageTimestamp) ? 1 : 0); + if (a.recentMessageTimestamp === null && b.recentMessageTimestamp === null) { + return 0; + } else if (a.recentMessageTimestamp === null) { + return 1; + } else if (b.recentMessageTimestamp === null) { + return -1; + } else if (b.recentMessageTimestamp < a.recentMessageTimestamp) { + return -1; + } else if (b.recentMessageTimestamp > a.recentMessageTimestamp) { + return 1; + } else { + return 0; + } }); return res.status(200).json({data: dmsData}) }) From 297619cacbea8939b0435aee1801da67b02412af Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 19 Nov 2019 19:02:13 -0500 Subject: [PATCH 11/22] Tweaking DM back-end functions --- functions/handlers/users.js | 4 ++-- functions/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 04dfa0b..0e73c0d 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -560,7 +560,7 @@ isDirectMessageEnabled = (username) => { // Assume DMs are enabled if they don't have a dmEnabled key resolve(result); } else { - result.code = 0; + result.code = 200; result.message = `${username} has DMs disabled`; reject(result); } @@ -846,7 +846,7 @@ exports.checkDirectMessagesEnabled = (req, res) => { }) .catch((result) => { console.log(result); - if (result.code === 0) { + if (result.code === 200) { // DMs are disabled return res.status(200).json({enabled: false}); } else { diff --git a/functions/index.js b/functions/index.js index ca249d0..cea6dc2 100644 --- a/functions/index.js +++ b/functions/index.js @@ -48,7 +48,7 @@ app.post("/dms/send", fbAuth, sendDirectMessage); app.post("/dms/new", fbAuth, createDirectMessage); // Checks if the user provided has DMs enabled or not -app.get("/dms/enabled", checkDirectMessagesEnabled); +app.post("/dms/enabled", checkDirectMessagesEnabled); app.get("/getUser", fbAuth, getUserDetails); From 3d9266778dd01ed09a6e63f48fb32ae8ef15a4c5 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Tue, 19 Nov 2019 19:04:25 -0500 Subject: [PATCH 12/22] Direct Messages can use redux for creating new DM channels --- twistter-frontend/src/App.js | 4 +- twistter-frontend/src/pages/directMessages.js | 322 ++++++++++-------- .../src/redux/actions/dataActions.js | 57 ++++ .../src/redux/reducers/dataReducer.js | 17 + .../src/redux/reducers/uiReducer.js | 47 ++- twistter-frontend/src/redux/types.js | 11 +- 6 files changed, 314 insertions(+), 144 deletions(-) diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index aeec8ee..4bd35ad 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -33,6 +33,7 @@ import editProfile from "./pages/editProfile"; import userLine from "./Userline.js"; import verify from "./pages/verify"; import Search from "./pages/Search.js"; +import directMessages from "./pages/directMessages"; const theme = createMuiTheme(themeObject); @@ -63,7 +64,7 @@ class App extends Component {
-
+
{/* AuthRoute checks if the user is logged in and if they are it redirects them to /home */} @@ -79,6 +80,7 @@ class App extends Component { + diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index ce05a43..10e802c 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -23,7 +23,7 @@ import SendIcon from '@material-ui/icons/Send'; // Redux import { connect } from 'react-redux'; -import { getDirectMessages } from '../redux/actions/dataActions'; +import { getDirectMessages, checkUsernameValid, createNewDirectMessage } from '../redux/actions/dataActions'; const styles = { pageContainer: { @@ -142,9 +142,9 @@ const styles = { marginTop: 8, marginLeft: 2 }, - loadingUsernameCheck: { - height: 85, - width: 85, + loadingUsernameChecks: { + height: 55, + width: 55, marginLeft: 5 }, errorIcon: { @@ -158,7 +158,14 @@ const styles = { width: 55, marginLeft: 5, color: '#1da1f2' - } + }, + createButton: { + // textAlign: "center", + // display: "block", + marginLeft: 96, + marginRight: 96, + position: "relative" + } }; export class directMessages extends Component { @@ -170,8 +177,8 @@ export class directMessages extends Component { dmData: null, anchorEl: null, createDMUsername: '', - checkingUsername: false, - usernameValid: false + usernameValid: false, + errors: null }; } @@ -186,117 +193,130 @@ export class directMessages extends Component { componentDidMount() { // this.props.getDirectMessages(); const resp = { - data: [ - { - recipient: 'batman', - messages: [], - recentMessage: null, - recentMessageTimestamp: null, - dmId: 'Lifb0XAONpNLJRhDnOHj' - }, - { - recipient: 'CrazyEddy', - messages: [ - { - messageId: 'yGqcBbDSM8TsoaQAfAVc', - author: 'CrazyEddy', - message: 'This is message 1', - createdAt: '2019-11-04T17:10:29.180Z' - }, - { - messageId: 'c1Bd1REkMBaMaraH10WP', - author: 'keanureeves', - message: 'This is message 2', - createdAt: '2019-11-04T17:33:35.169Z' - }, - { - messageId: 'CL5sThnuekks6579MKuF', - author: 'keanureeves', - message: 'Yo, this my first message', - createdAt: '2019-11-08T22:15:08.456Z' - }, - { - messageId: 'BgMSSlLLLdC1DMtJl5VJ', - author: 'CrazyEddy', - message: 'That is epic', - createdAt: '2019-11-08T22:20:16.768Z' - }, - { - message: 'test test test', - createdAt: '2019-11-08T22:20:58.961Z', - messageId: '9AeUuz0l4wWyQSG6RQ4A', - author: 'keanureeves' - }, - { - messageId: 'zhfEpirBK7jl9FnFMtsQ', - author: 'CrazyEddy', - message: 'noice', - createdAt: '2019-11-08T22:21:29.768Z' - }, - { - message: 'What time is it?', - createdAt: '2019-11-08T22:23:27.353Z', - messageId: 'XwyKwFU2L5wrTKedzlyF', - author: 'CrazyEddy' - }, - { - message: "it's 5:24 right now", - createdAt: '2019-11-08T22:24:21.807Z', - messageId: 'qeasHYkAtTGvjnc3VJAi', - author: 'keanureeves' - }, - { - messageId: 'I7kzyLUd9Pp5qzxTPzQv', - author: 'keanureeves', - message: 'a', - createdAt: '2019-11-08T22:31:42.852Z' - }, - { - messageId: 'iySWBDFFrbY8FT6E61NL', - author: 'keanureeves', - message: 'b', - createdAt: '2019-11-08T22:31:51.558Z' - }, - { - messageId: 'Yis0vXSEuMggj6z5Mq1a', - author: 'keanureeves', - message: 'c', - createdAt: '2019-11-08T22:32:01.293Z' - }, - { - messageId: 'DIgjDvFczqO0OWkOrL0t', - author: 'keanureeves', - message: 'd', - createdAt: '2019-11-08T22:32:16.095Z' - }, - { - message: 'e', - createdAt: '2019-11-08T22:32:22.134Z', - messageId: '6h1dnFE440MOrjySEQHU', - author: 'keanureeves' - }, - { - messageId: 'lmOGGYUWZyB3xG38T7lG', - author: 'keanureeves', - message: 'f', - createdAt: '2019-11-08T22:32:28.424Z' - }, - { - message: 'g', - createdAt: '2019-11-08T22:32:37.632Z', - messageId: '64swTj7yiFy7SF6BPbki', - author: 'keanureeves' - } - ], - recentMessage: 'g', - recentMessageTimestamp: '2019-11-08T22:32:37.632Z', - dmId: 'avGcIs4PFCJhc4EDqAfe' - } - ] - }; + "data": [ + { + "recipient": "CrazyEddy", + "messages": [ + { + "message": "This is message 1", + "createdAt": "2019-11-04T17:10:29.180Z", + "messageId": "yGqcBbDSM8TsoaQAfAVc", + "author": "CrazyEddy" + }, + { + "messageId": "c1Bd1REkMBaMaraH10WP", + "author": "keanureeves", + "message": "This is message 2", + "createdAt": "2019-11-04T17:33:35.169Z" + }, + { + "messageId": "CL5sThnuekks6579MKuF", + "author": "keanureeves", + "message": "Yo, this my first message", + "createdAt": "2019-11-08T22:15:08.456Z" + }, + { + "messageId": "BgMSSlLLLdC1DMtJl5VJ", + "author": "CrazyEddy", + "message": "That is epic", + "createdAt": "2019-11-08T22:20:16.768Z" + }, + { + "message": "test test test", + "createdAt": "2019-11-08T22:20:58.961Z", + "messageId": "9AeUuz0l4wWyQSG6RQ4A", + "author": "keanureeves" + }, + { + "messageId": "zhfEpirBK7jl9FnFMtsQ", + "author": "CrazyEddy", + "message": "noice", + "createdAt": "2019-11-08T22:21:29.768Z" + }, + { + "message": "What time is it?", + "createdAt": "2019-11-08T22:23:27.353Z", + "messageId": "XwyKwFU2L5wrTKedzlyF", + "author": "CrazyEddy" + }, + { + "messageId": "qeasHYkAtTGvjnc3VJAi", + "author": "keanureeves", + "message": "it's 5:24 right now", + "createdAt": "2019-11-08T22:24:21.807Z" + }, + { + "messageId": "I7kzyLUd9Pp5qzxTPzQv", + "author": "keanureeves", + "message": "a", + "createdAt": "2019-11-08T22:31:42.852Z" + }, + { + "messageId": "iySWBDFFrbY8FT6E61NL", + "author": "keanureeves", + "message": "b", + "createdAt": "2019-11-08T22:31:51.558Z" + }, + { + "messageId": "Yis0vXSEuMggj6z5Mq1a", + "author": "keanureeves", + "message": "c", + "createdAt": "2019-11-08T22:32:01.293Z" + }, + { + "messageId": "DIgjDvFczqO0OWkOrL0t", + "author": "keanureeves", + "message": "d", + "createdAt": "2019-11-08T22:32:16.095Z" + }, + { + "messageId": "6h1dnFE440MOrjySEQHU", + "author": "keanureeves", + "message": "e", + "createdAt": "2019-11-08T22:32:22.134Z" + }, + { + "messageId": "lmOGGYUWZyB3xG38T7lG", + "author": "keanureeves", + "message": "f", + "createdAt": "2019-11-08T22:32:28.424Z" + }, + { + "messageId": "64swTj7yiFy7SF6BPbki", + "author": "keanureeves", + "message": "g", + "createdAt": "2019-11-08T22:32:37.632Z" + } + ], + "recentMessage": "g", + "recentMessageTimestamp": "2019-11-08T22:32:37.632Z", + "dmId": "avGcIs4PFCJhc4EDqAfe" + }, + { + "recipient": "batman", + "messages": [], + "recentMessage": null, + "recentMessageTimestamp": null, + "dmId": "Lifb0XAONpNLJRhDnOHj" + }, + { + "recipient": "katherine", + "messages": [], + "recentMessage": null, + "recentMessageTimestamp": null, + "dmId": "QBMonxPTbha0uJc7P73R" + } + ] + } // const resp = {"data": null} this.setState({ dmData: resp.data }); - } + } + + // componentWillReceiveProps(nextProps) { + // if (nextProps.data.dmData) { + // this.setState({ dmData: nextProps.data.dmData }); + // } + // } // Handles selecting different DM channels handleClickChannel = (event) => { @@ -343,21 +363,27 @@ export class directMessages extends Component { handleCloseAddDMPopover = () => { this.setState({ anchorEl: null, - createDMUsername: '', - checkingUsername: false, + createDMUsername: '', usernameValid: false }); }; handleChangeAddDMUsername = (event) => { this.setState({ - checkingUsername: true, createDMUsername: event.target.value - }); - }; + }); + }; + + handleClickCreate = () => { + this.props.createNewDirectMessage(this.state.createDMUsername); + } render() { - const { classes } = this.props; + const { classes } = this.props; + const loadingDirectMessages = this.props.UI.loading2; + const creatingDirectMessage = this.props.UI.loading3; + const sendingDirectMessage = this.props.UI.loading4; + let errors = this.props.UI.errors ? this.props.UI.errors : {}; dayjs.extend(relativeTime); // Used for the add button on the dmList @@ -488,7 +514,7 @@ export class directMessages extends Component { - + Who would you like to start a DM with? @@ -497,23 +523,35 @@ export class directMessages extends Component { onChange={this.handleChangeAddDMUsername} value={this.state.createDMUsername} label="Username" - variant="outlined" + variant="outlined" + helperText={errors.createDirectMessage} + error={errors.createDirectMessage ? true : false} style={{ - margin: 'auto', - textAlign: 'center' + width: 265, + marginRight: 10, + marginLeft: 10, + textAlign: 'center', }} /> - {this.state.checkingUsername && ( - - ) // Won't accept classes style for some reason - } - {!this.state.usernameValid && - !this.state.checkingUsername && } - {this.state.usernameValid && - !this.state.checkingUsername && ( - - )} + + + @@ -531,7 +569,9 @@ export class directMessages extends Component { {dmListMarkup} - {addDMMarkup} + + {addDMMarkup} + @@ -554,7 +594,7 @@ export class directMessages extends Component { margin="dense" /> - + @@ -571,7 +611,8 @@ export class directMessages extends Component { directMessages.propTypes = { classes: PropTypes.object.isRequired, - getDirectMessages: PropTypes.func.isRequired, + getDirectMessages: PropTypes.func.isRequired, + createNewDirectMessage: PropTypes.func.isRequired, user: PropTypes.object.isRequired, UI: PropTypes.object.isRequired }; @@ -583,7 +624,8 @@ const mapStateToProps = (state) => ({ }); const mapActionsToProps = { - getDirectMessages + getDirectMessages, + createNewDirectMessage }; export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(directMessages)); diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js index e69de29..620fc61 100644 --- a/twistter-frontend/src/redux/actions/dataActions.js +++ b/twistter-frontend/src/redux/actions/dataActions.js @@ -0,0 +1,57 @@ +import { + SET_DIRECT_MESSAGES, + LOADING_UI, + SET_ERRORS, + CLEAR_ERRORS, + SET_LOADING_UI_2, + SET_LOADING_UI_3, + SET_LOADING_UI_4, + SET_NOT_LOADING_UI_2, + SET_NOT_LOADING_UI_3, + SET_NOT_LOADING_UI_4 +} from '../types'; +import axios from "axios"; + +export const getDirectMessages = () => (dispatch) => { + dispatch({type: LOADING_UI}); + axios.get('/dms') + .then((res) => { + dispatch({ + type: SET_DIRECT_MESSAGES, + payload: res.data.data + }); + dispatch({type: CLEAR_ERRORS}); + }) +} + +export const createNewDirectMessage = (username) => (dispatch) => { + dispatch({type: SET_LOADING_UI_3}); + const data = { + user: username + } + // console.log(username); + + axios.post('/dms/new', data) + .then((res) => { + console.log(res.data); + if (res.data.err) { + dispatch({ + type: SET_ERRORS, + payload: { + createDirectMessage: res.data.err + } + }); + } + dispatch({type: SET_NOT_LOADING_UI_3}); + }) + .catch((err) => { + dispatch({ + type: SET_ERRORS, + payload: { + createDirectMessage: err.response.data.err + } + }); + dispatch({type: SET_NOT_LOADING_UI_3}); + console.log(err.response.data); + }) +} \ No newline at end of file diff --git a/twistter-frontend/src/redux/reducers/dataReducer.js b/twistter-frontend/src/redux/reducers/dataReducer.js index e69de29..5302d64 100644 --- a/twistter-frontend/src/redux/reducers/dataReducer.js +++ b/twistter-frontend/src/redux/reducers/dataReducer.js @@ -0,0 +1,17 @@ +import {SET_DIRECT_MESSAGES, SET_USERNAME_VALID, SET_USERNAME_INVALID} from '../types'; + +const initialState = { + directMessages: null, +}; + +export default function(state = initialState, action) { + switch(action.type) { + case SET_DIRECT_MESSAGES: + return { + ...state, + directMessages: action.payload + }; + default: + return state; + } +} \ No newline at end of file diff --git a/twistter-frontend/src/redux/reducers/uiReducer.js b/twistter-frontend/src/redux/reducers/uiReducer.js index 65e781d..3188cc1 100644 --- a/twistter-frontend/src/redux/reducers/uiReducer.js +++ b/twistter-frontend/src/redux/reducers/uiReducer.js @@ -1,7 +1,20 @@ -import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types'; +import { + SET_ERRORS, + CLEAR_ERRORS, + LOADING_UI, + SET_LOADING_UI_2, + SET_LOADING_UI_3, + SET_LOADING_UI_4, + SET_NOT_LOADING_UI_2, + SET_NOT_LOADING_UI_3, + SET_NOT_LOADING_UI_4 + } from '../types'; const initialState = { loading: false, + loading2: false, + loading3: false, + loading4: false, errors: null }; @@ -23,7 +36,37 @@ export default function(state = initialState, action) { return { ...state, loading: true - } + }; + case SET_LOADING_UI_2: + return { + ...state, + loading2: true + }; + case SET_LOADING_UI_3: + return { + ...state, + loading3: true + }; + case SET_LOADING_UI_4: + return { + ...state, + loading4: true + }; + case SET_NOT_LOADING_UI_2: + return { + ...state, + loading2: false + }; + case SET_NOT_LOADING_UI_3: + return { + ...state, + loading3: false + }; + case SET_NOT_LOADING_UI_4: + return { + ...state, + loading4: false + }; default: return state; } diff --git a/twistter-frontend/src/redux/types.js b/twistter-frontend/src/redux/types.js index 6f16afc..d1e11e8 100644 --- a/twistter-frontend/src/redux/types.js +++ b/twistter-frontend/src/redux/types.js @@ -7,6 +7,15 @@ export const LOADING_USER = 'LOADING_USER'; // UI reducer types export const SET_ERRORS = 'SET_ERRORS'; export const LOADING_UI = 'LOADING_UI'; +export const SET_LOADING_UI_2 = 'SET_LOADING_UI_2'; +export const SET_LOADING_UI_3 = 'SET_LOADING_UI_3'; +export const SET_LOADING_UI_4 = 'SET_LOADING_UI_4'; +export const SET_NOT_LOADING_UI_2 = 'SET_NOT_LOADING_UI_2'; +export const SET_NOT_LOADING_UI_3 = 'SET_NOT_LOADING_UI_3'; +export const SET_NOT_LOADING_UI_4 = 'SET_NOT_LOADING_UI_4'; export const CLEAR_ERRORS = 'CLEAR_ERRORS'; -// Data reducer types \ No newline at end of file +// Data reducer types +export const SET_DIRECT_MESSAGES = 'SET_DIRECT_MESSAGES'; +export const SET_USERNAME_VALID = 'SET_USERNAME_VALID'; +export const SET_USERNAME_INVALID = 'SET_USERNAME_INVALID'; \ No newline at end of file From 709df1492d864534cf551ba0bd1d87e204a5032d Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 20 Nov 2019 20:32:11 -0500 Subject: [PATCH 13/22] Changed how back-end DMs worked --- functions/handlers/users.js | 210 +++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 77 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 0e73c0d..9c13564 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -483,6 +483,9 @@ exports.getDirectMessages = (req, res) => { dmData.recipient = docData.authors[1] : dmData.recipient = docData.authors[0] + // Save the createdAt time + dmData.createdAt = docData.createdAt; + // Get all the messages from this dm document getMessages(dm) .then((msgs) => { @@ -515,7 +518,13 @@ exports.getDirectMessages = (req, res) => { // Sort the DMs so that the ones with the newest messages are at the top dmsData.sort((a, b) => { if (a.recentMessageTimestamp === null && b.recentMessageTimestamp === null) { - return 0; + if (b.createdAt < a.createdAt) { + return -1; + } else if (b.createdAt > a.createdAt) { + return 1; + } else { + return 0; + } } else if (a.recentMessageTimestamp === null) { return 1; } else if (b.recentMessageTimestamp === null) { @@ -589,97 +598,143 @@ verifyDirectMessageIntegrity = (dmRef) => { }) } + +// Checks if there are any DM channels open with userB on userA's side +oneWayCheck = (userA, userB) => { + return new Promise((resolve, reject) => { + + db.doc(`/users/${userA}`) + .get() + .then((userASnapshot) => { + const dmList = userASnapshot.data().dms; + const dmRecipients = userASnapshot.data().dmRecipients; + + if (dmList === null || dmList === undefined || dmRecipients === null || dmRecipients === undefined) { + // They don't have any DMs yet + console.log("No DMs array"); + userASnapshot.ref.set({dms:[], dmRecipients:[]}, {merge: true}) + .then(() => { + resolve(); + }) + } else if (dmList.length === 0) { + // Their DMs are empty + console.log("DMs array is empty"); + resolve(); + } else { + // let dmDocs = []; + // let forEachPromises = []; + // dmList.forEach((dmRef) => { + // forEachPromises.push( + // dmRef.get() + // // .then((dmDoc) => { + // // TODO: Figure out why dmDoc.exists() isn't working + // // Make sure all of the docs exist and none of the references + // // are broken + // // if (dmDoc.exists()) { + // // dmDocs.push(dmDoc); + // // } else { + // // console.log(`DM reference /dm/${dmDoc.id} is invalid`); + // // reject(`DM reference /dm/${dmDoc.id} is invalid`); + // // } + // // }) + // ) + // }) + + dmRecipients.forEach((dmRecipient) => { + if (dmRecipient === userB) { + console.log(`You already have a DM with ${userB}`); + reject(`You already have a DM with ${userB}`); + } + }) + + resolve(); + + + // Promise.all(forEachPromises) + // .then((dmDocs) => { + // // Check if any of the DMs have for userA have userA and userB as the authors. + // // This would mean that they already have a DM channel + // dmDocs.forEach((dmDoc) => { + // // Checking if any of the authors key in any of their DMs are missing + // let authors = dmDoc.data().authors; + + // // if (authors[0] === "keanureeves") { + // // console.log("it is") + // // resolve(); + // // } else { + // // console.log("it is not") + // // reject("not my keanu"); + // // } + // // if (authors === null || authors === undefined || authors.length !== 2) { + // // // console.log(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + // // // reject(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + // // console.log('a') + // // reject("a") + // // } else if ((authors[0] === userA && authors[1] === userB) || (authors[1] === userA && authors[0] === userB)) { + // // // console.log(`${userA} already has a DM channel between ${userA} and ${userB}`); + // // // reject(`${userA} already has a DM channel between ${userA} and ${userB}`); + // // console.log('b') + // // reject('b') + // // } else { + // // // BUG: For some reason the promise.all is resolving even though there are multiple rejects + // // // and only one resolve + // // console.log("c"); + // // resolve(); + // // } + // // console.log(authors) + // // console.log([userA, userB]) + // if (authors[0] === null || authors === undefined || authors.length !== 2) { + // console.log('a'); + // reject('a'); + // } else if (authors[0] === userA && authors[1] === userB) { + // console.log("b"); + // reject('b'); + // } else { + // console.log('c'); + // resolve(); + // } + // }) + // }) + + + + } + }) + }) + +} + + // Returns a promise that resolves if there is not already a DM channel // between the creator and recipient usernames. It rejects if one already // exists or there is an error. checkNoDirectMessageExists = (creator, recipient) => { - - // Checks if there are any DM channels open with userB on userA's side - oneWayCheck = (userA, userB) => { - return new Promise((resolve, reject) => { - db.doc(`/users/${userA}`) - .get() - .then((userASnapshot) => { - const dmList = userASnapshot.data().dms; - if (dmList === null || dmList === undefined) { - // They don't have any DMs yet - userASnapshot.ref.set({dms:[]}, {merge: true}) - .then(() => { - resolve(); - }) - } else if (dmList.length === 0) { - // Their DMs are empty - resolve(); - } else { - let dmDocs = []; - let forEachPromises = []; - dmList.forEach((dmRef) => { - forEachPromises.push( - dmRef.get() - .then((dmDoc) => { - // TODO: Figure out why dmDoc.exists() isn't working - // Make sure all of the docs exist and none of the references - // are broken - // if (dmDoc.exists()) { - dmDocs.push(dmDoc); - // } else { - // console.log(`DM reference /dm/${dmDoc.id} is invalid`); - // reject(`DM reference /dm/${dmDoc.id} is invalid`); - // } - }) - ) - }) - Promise.all(forEachPromises) - .then(() => { - // Check if any of the DMs have for userA have userA and userB as the authors. - // This would mean that they already have a DM channel - dmDocs.forEach((dmDoc) => { - // Checking if any of the authors key in any of their DMs are missing - const authors = dmDoc.data().authors; - if (authors === null || authors === undefined || authors.length !== 2) { - console.log(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); - reject(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); - } - - if ( - (authors[0] === userA && authors[1] === userB) || - (authors[1] === userA && authors[0] === userB) - ) { - console.log(`${userA} already has a DM channel between ${userA} and ${userB}`); - reject(`${userA} already has a DM channel between ${userA} and ${userB}`); - } else { - resolve(); - } - }) - }) - } - }) - }) - - } - return new Promise((resolve, reject) => { let creatorPromise = oneWayCheck(creator, recipient); let recipientPromise = oneWayCheck(recipient, creator); + let temp_array = []; + temp_array.push(creatorPromise); + temp_array.push(recipientPromise) - Promise.all([creatorPromise, recipientPromise]) + Promise.all(temp_array) .then(() => { resolve(); }) .catch((err) => { - console.log(err) reject(err); }) }) } -addDirectMessageToUser = (username, dmRef) => { +addDirectMessageToUser = (username, recipient, dmRef) => { return new Promise((resolve, reject) => { db.doc(`/users/${username}`).get() .then((docSnap) => { let dmList = docSnap.data().dms; - dmList.push(dmRef) - return db.doc(`/users/${username}`).update({dms: dmList}); + let dmRecipients = docSnap.data().dmRecipients; + dmList.push(dmRef); + dmRecipients.push(recipient); + return db.doc(`/users/${username}`).update({dms: dmList, dmRecipients}); }) .then(() => { resolve(); @@ -776,7 +831,7 @@ exports.createDirectMessage = (req, res) => { const recipient = req.body.user; // Check if they are DMing themselves - if (creator === recipient) return res.status(400).json({error: "A user cannot DM themselves"}); + if (creator === recipient) return res.status(400).json({error: "You can't DM yourself"}); // Check if this user has DMs enabled let creatorEnabled = isDirectMessageEnabled(creator); @@ -804,17 +859,18 @@ exports.createDirectMessage = (req, res) => { // Note that there isn't a messages collection by default. let dmData = { dmId: dmDocRef.id, - authors: [creator, recipient] + authors: [creator, recipient], + createdAt: new Date().toISOString() } // Update DM document let dmDocPromise = dmDocRef.set(dmData); // Add the DM reference to the creator - let updateCreatorPromise = addDirectMessageToUser(creator, dmDocRef); + let updateCreatorPromise = addDirectMessageToUser(creator, recipient, dmDocRef); // Add the DM reference to the recipient - let updateRecipientPromise = addDirectMessageToUser(recipient, dmDocRef); + let updateRecipientPromise = addDirectMessageToUser(recipient, creator, dmDocRef); // Wait for all promises return Promise.all([dmDocPromise, updateCreatorPromise, updateRecipientPromise]); @@ -827,10 +883,10 @@ exports.createDirectMessage = (req, res) => { if (err.code && err.message && err.code > 0) { // Specific error that I've created - return res.status(err.code).json({err: err.message}); + return res.status(err.code).json({error: err.message}); } else { // Generic or firebase error - return res.status(500).json({err}); + return res.status(500).json({error: err}); } }) } From e06cc614cb65cb81ecb9db54f21958ece88da87c Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 20 Nov 2019 20:48:49 -0500 Subject: [PATCH 14/22] Dynamic front-end adding new DM channels --- twistter-frontend/src/pages/directMessages.js | 195 +++++------------- .../src/redux/actions/dataActions.js | 49 ++++- 2 files changed, 102 insertions(+), 142 deletions(-) diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index 10e802c..1b72e7b 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -23,7 +23,7 @@ import SendIcon from '@material-ui/icons/Send'; // Redux import { connect } from 'react-redux'; -import { getDirectMessages, checkUsernameValid, createNewDirectMessage } from '../redux/actions/dataActions'; +import { getDirectMessages, createNewDirectMessage, getNewDirectMessages, reloadDirectMessageChannels } from '../redux/actions/dataActions'; const styles = { pageContainer: { @@ -34,11 +34,18 @@ const styles = { }, dmList: { width: 300, - marginLeft: 15 - }, - dmItem: { - marginBottom: 1 + marginLeft: 15 }, + dmItemsUpper: { + marginBottom: 1, + // height: 'calc(100vh - 50px - 142px)', + minHeight: 100, + maxHeight: 'calc(100vh - 50px - 142px)', + overflow: "auto" + }, + dmItemsLower: { + + }, dmItemUsernameSelected: { fontSize: 20, color: 'white' @@ -191,132 +198,27 @@ export class directMessages extends Component { } componentDidMount() { - // this.props.getDirectMessages(); - const resp = { - "data": [ - { - "recipient": "CrazyEddy", - "messages": [ - { - "message": "This is message 1", - "createdAt": "2019-11-04T17:10:29.180Z", - "messageId": "yGqcBbDSM8TsoaQAfAVc", - "author": "CrazyEddy" - }, - { - "messageId": "c1Bd1REkMBaMaraH10WP", - "author": "keanureeves", - "message": "This is message 2", - "createdAt": "2019-11-04T17:33:35.169Z" - }, - { - "messageId": "CL5sThnuekks6579MKuF", - "author": "keanureeves", - "message": "Yo, this my first message", - "createdAt": "2019-11-08T22:15:08.456Z" - }, - { - "messageId": "BgMSSlLLLdC1DMtJl5VJ", - "author": "CrazyEddy", - "message": "That is epic", - "createdAt": "2019-11-08T22:20:16.768Z" - }, - { - "message": "test test test", - "createdAt": "2019-11-08T22:20:58.961Z", - "messageId": "9AeUuz0l4wWyQSG6RQ4A", - "author": "keanureeves" - }, - { - "messageId": "zhfEpirBK7jl9FnFMtsQ", - "author": "CrazyEddy", - "message": "noice", - "createdAt": "2019-11-08T22:21:29.768Z" - }, - { - "message": "What time is it?", - "createdAt": "2019-11-08T22:23:27.353Z", - "messageId": "XwyKwFU2L5wrTKedzlyF", - "author": "CrazyEddy" - }, - { - "messageId": "qeasHYkAtTGvjnc3VJAi", - "author": "keanureeves", - "message": "it's 5:24 right now", - "createdAt": "2019-11-08T22:24:21.807Z" - }, - { - "messageId": "I7kzyLUd9Pp5qzxTPzQv", - "author": "keanureeves", - "message": "a", - "createdAt": "2019-11-08T22:31:42.852Z" - }, - { - "messageId": "iySWBDFFrbY8FT6E61NL", - "author": "keanureeves", - "message": "b", - "createdAt": "2019-11-08T22:31:51.558Z" - }, - { - "messageId": "Yis0vXSEuMggj6z5Mq1a", - "author": "keanureeves", - "message": "c", - "createdAt": "2019-11-08T22:32:01.293Z" - }, - { - "messageId": "DIgjDvFczqO0OWkOrL0t", - "author": "keanureeves", - "message": "d", - "createdAt": "2019-11-08T22:32:16.095Z" - }, - { - "messageId": "6h1dnFE440MOrjySEQHU", - "author": "keanureeves", - "message": "e", - "createdAt": "2019-11-08T22:32:22.134Z" - }, - { - "messageId": "lmOGGYUWZyB3xG38T7lG", - "author": "keanureeves", - "message": "f", - "createdAt": "2019-11-08T22:32:28.424Z" - }, - { - "messageId": "64swTj7yiFy7SF6BPbki", - "author": "keanureeves", - "message": "g", - "createdAt": "2019-11-08T22:32:37.632Z" - } - ], - "recentMessage": "g", - "recentMessageTimestamp": "2019-11-08T22:32:37.632Z", - "dmId": "avGcIs4PFCJhc4EDqAfe" - }, - { - "recipient": "batman", - "messages": [], - "recentMessage": null, - "recentMessageTimestamp": null, - "dmId": "Lifb0XAONpNLJRhDnOHj" - }, - { - "recipient": "katherine", - "messages": [], - "recentMessage": null, - "recentMessageTimestamp": null, - "dmId": "QBMonxPTbha0uJc7P73R" - } - ] - } - // const resp = {"data": null} - this.setState({ dmData: resp.data }); + this.props.getDirectMessages(); + // this.updatePage(); } - // componentWillReceiveProps(nextProps) { - // if (nextProps.data.dmData) { - // this.setState({ dmData: nextProps.data.dmData }); - // } - // } + // Updates the state whenever redux is updated + componentWillReceiveProps(nextProps) { + if (nextProps.directMessages) { + this.setState({ dmData: nextProps.directMessages}); + } + } + + updatePage = async() => { + while (true) { + await this.sleep(15000); + this.props.getNewDirectMessages(); + } + } + + sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); + } // Handles selecting different DM channels handleClickChannel = (event) => { @@ -375,7 +277,17 @@ export class directMessages extends Component { }; handleClickCreate = () => { - this.props.createNewDirectMessage(this.state.createDMUsername); + this.props.createNewDirectMessage(this.state.createDMUsername) + .then(() => { + return this.props.reloadDirectMessageChannels(); + }) + .then(() => { + this.handleCloseAddDMPopover(); + return; + }) + .catch(() => { + return; + }) } render() { @@ -390,7 +302,7 @@ export class directMessages extends Component { const open = Boolean(this.state.anchorEl); const id = open ? 'simple-popover' : undefined; - let dmListMarkup = this.state.dmData ? ( + let dmListMarkup = this.state.dmData ? ( this.state.dmData.map((channel) => ( You don't have any DMs yet

- ); + ) let messagesMarkup = this.state.selectedChannel !== null ? this.state.selectedChannel.messages.length > 0 ? ( @@ -562,18 +474,21 @@ export class directMessages extends Component { ); return ( + loadingDirectMessages ? : - + {dmListMarkup} - + + + {addDMMarkup} - + @@ -599,7 +514,7 @@ export class directMessages extends Component {
)} - {!this.state.hasChannelSelected && + {!this.state.hasChannelSelected && this.state.dmData && Select a DM on the left} @@ -613,6 +528,8 @@ directMessages.propTypes = { classes: PropTypes.object.isRequired, getDirectMessages: PropTypes.func.isRequired, createNewDirectMessage: PropTypes.func.isRequired, + getNewDirectMessages: PropTypes.func.isRequired, + reloadDirectMessageChannels: PropTypes.func.isRequired, user: PropTypes.object.isRequired, UI: PropTypes.object.isRequired }; @@ -620,12 +537,14 @@ directMessages.propTypes = { const mapStateToProps = (state) => ({ user: state.user, UI: state.UI, - directMessages: state.directMessages + directMessages: state.data.directMessages }); const mapActionsToProps = { getDirectMessages, - createNewDirectMessage + createNewDirectMessage, + getNewDirectMessages, + reloadDirectMessageChannels }; export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(directMessages)); diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js index 620fc61..21e8304 100644 --- a/twistter-frontend/src/redux/actions/dataActions.js +++ b/twistter-frontend/src/redux/actions/dataActions.js @@ -13,19 +13,52 @@ import { import axios from "axios"; export const getDirectMessages = () => (dispatch) => { - dispatch({type: LOADING_UI}); + dispatch({type: SET_LOADING_UI_2}); axios.get('/dms') .then((res) => { dispatch({ type: SET_DIRECT_MESSAGES, payload: res.data.data }); + dispatch({type: SET_NOT_LOADING_UI_2}); dispatch({type: CLEAR_ERRORS}); }) } +export const getNewDirectMessages = () => (dispatch) => { + return new Promise((resolve, reject) => { + axios.get('/dms') + .then((res) => { + dispatch({ + type: SET_DIRECT_MESSAGES, + payload: res.data.data + }); + dispatch({type: SET_NOT_LOADING_UI_2}); + dispatch({type: CLEAR_ERRORS}); + resolve(); + }) + }) +} + +export const reloadDirectMessageChannels = () => (dispatch) => { + return new Promise((resolve, reject) => { + axios.get('/dms') + .then((res) => { + dispatch({ + type: SET_DIRECT_MESSAGES, + payload: res.data.data + }); + dispatch({type: SET_NOT_LOADING_UI_3}); + dispatch({type: CLEAR_ERRORS}); + resolve(); + }) + }) + +} + export const createNewDirectMessage = (username) => (dispatch) => { - dispatch({type: SET_LOADING_UI_3}); + return new Promise((resolve, reject) => { + dispatch({type: SET_LOADING_UI_3}); const data = { user: username } @@ -41,17 +74,25 @@ export const createNewDirectMessage = (username) => (dispatch) => { createDirectMessage: res.data.err } }); + dispatch({type: SET_NOT_LOADING_UI_3}); + } else { + // dispatch(getNewDirectMessages()); + // dispatch({type: SET_NOT_LOADING_UI_3}); } - dispatch({type: SET_NOT_LOADING_UI_3}); + resolve(); }) .catch((err) => { dispatch({ type: SET_ERRORS, payload: { - createDirectMessage: err.response.data.err + createDirectMessage: err.response.data.error } }); dispatch({type: SET_NOT_LOADING_UI_3}); console.log(err.response.data); + reject(); }) + }); + + } \ No newline at end of file From 331509da7fdcf6bbf351e54e5901a0c070409ba8 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 22 Nov 2019 16:44:53 -0500 Subject: [PATCH 15/22] Direct Messages sending DMs --- twistter-frontend/package.json | 1 + twistter-frontend/src/pages/directMessages.js | 73 ++++++++++++++++--- .../src/redux/actions/dataActions.js | 73 +++++++++++++------ 3 files changed, 116 insertions(+), 31 deletions(-) diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index f7dd12f..a0ec11c 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -10,6 +10,7 @@ "axios": "^0.19.0", "clsx": "^1.0.4", "create-react-app": "^3.1.2", + "dayjs": "^1.8.17", "install": "^0.13.0", "jwt-decode": "^2.2.0", "node-pre-gyp": "^0.13.0", diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index 1b72e7b..63fb8b0 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -23,7 +23,13 @@ import SendIcon from '@material-ui/icons/Send'; // Redux import { connect } from 'react-redux'; -import { getDirectMessages, createNewDirectMessage, getNewDirectMessages, reloadDirectMessageChannels } from '../redux/actions/dataActions'; +import { + getDirectMessages, + createNewDirectMessage, + getNewDirectMessages, + reloadDirectMessageChannels, + sendDirectMessage +} from '../redux/actions/dataActions'; const styles = { pageContainer: { @@ -185,6 +191,8 @@ export class directMessages extends Component { anchorEl: null, createDMUsername: '', usernameValid: false, + // message: '', + drafts: {}, errors: null }; } @@ -205,7 +213,17 @@ export class directMessages extends Component { // Updates the state whenever redux is updated componentWillReceiveProps(nextProps) { if (nextProps.directMessages) { - this.setState({ dmData: nextProps.directMessages}); + this.setState({ dmData: nextProps.directMessages}, () => { + if (this.state.selectedChannel) { + this.state.dmData.forEach((channel) => { + if (channel.dmId === this.state.selectedChannel.dmId) { + this.setState({ + selectedChannel: channel + }); + } + }); + } + }); } } @@ -290,6 +308,26 @@ export class directMessages extends Component { }) } + handleChangeMessage = (event) => { + let drafts = this.state.drafts; + drafts[this.state.selectedChannel.dmId] = event.target.value; + this.setState({ + drafts + }); + } + + handleClickSend = () => { + // console.log(this.state.drafts[this.state.selectedChannel.dmId]); + let drafts = this.state.drafts; + if (this.state.hasChannelSelected && drafts[this.state.selectedChannel.dmId]) { + this.props.sendDirectMessage(this.state.selectedChannel.recipient, drafts[this.state.selectedChannel.dmId]); + drafts[this.state.selectedChannel.dmId] = null; + this.setState({ + drafts + }); + } + } + render() { const { classes } = this.props; const loadingDirectMessages = this.props.UI.loading2; @@ -309,7 +347,7 @@ export class directMessages extends Component { key={channel.dmId} data-key={channel.dmId} className={ - this.state.selectedChannel === channel ? classes.dmCardSelected : classes.dmCardUnselected + this.state.selectedChannel && this.state.selectedChannel.dmId === channel.dmId ? classes.dmCardSelected : classes.dmCardUnselected } > @@ -318,7 +356,7 @@ export class directMessages extends Component { - + + { + sendingDirectMessage && + + // Won't accept classes style for some reason + } @@ -530,6 +583,7 @@ directMessages.propTypes = { createNewDirectMessage: PropTypes.func.isRequired, getNewDirectMessages: PropTypes.func.isRequired, reloadDirectMessageChannels: PropTypes.func.isRequired, + sendDirectMessage: PropTypes.func.isRequired, user: PropTypes.object.isRequired, UI: PropTypes.object.isRequired }; @@ -544,7 +598,8 @@ const mapActionsToProps = { getDirectMessages, createNewDirectMessage, getNewDirectMessages, - reloadDirectMessageChannels + reloadDirectMessageChannels, + sendDirectMessage }; export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(directMessages)); diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js index 21e8304..d387b6a 100644 --- a/twistter-frontend/src/redux/actions/dataActions.js +++ b/twistter-frontend/src/redux/actions/dataActions.js @@ -59,40 +59,69 @@ export const reloadDirectMessageChannels = () => (dispatch) => { export const createNewDirectMessage = (username) => (dispatch) => { return new Promise((resolve, reject) => { dispatch({type: SET_LOADING_UI_3}); - const data = { - user: username - } - // console.log(username); + const data = { + user: username + } + // console.log(username); - axios.post('/dms/new', data) - .then((res) => { - console.log(res.data); - if (res.data.err) { + axios.post('/dms/new', data) + .then((res) => { + console.log(res.data); + if (res.data.err) { + dispatch({ + type: SET_ERRORS, + payload: { + createDirectMessage: res.data.err + } + }); + dispatch({type: SET_NOT_LOADING_UI_3}); + } else { + // dispatch(getNewDirectMessages()); + // dispatch({type: SET_NOT_LOADING_UI_3}); + } + resolve(); + }) + .catch((err) => { dispatch({ type: SET_ERRORS, payload: { - createDirectMessage: res.data.err + createDirectMessage: err.response.data.error } }); dispatch({type: SET_NOT_LOADING_UI_3}); - } else { - // dispatch(getNewDirectMessages()); - // dispatch({type: SET_NOT_LOADING_UI_3}); - } - resolve(); + console.log(err.response.data); + reject(); + }) + }); +} + +export const sendDirectMessage = (user, message) => (dispatch) => { + dispatch({type: SET_LOADING_UI_4}); + const data = { + message, + user + }; + + axios.post('/dms/send', data) + .then((res) => { + // console.log(res); + return axios.get('/dms') + }) + .then((res) => { + dispatch({ + type: SET_DIRECT_MESSAGES, + payload: res.data.data + }); + dispatch({type: SET_NOT_LOADING_UI_4}); + dispatch({type: CLEAR_ERRORS}); }) .catch((err) => { + console.log(err); dispatch({ type: SET_ERRORS, payload: { - createDirectMessage: err.response.data.error + sendDirectMessage: err.response.data } - }); - dispatch({type: SET_NOT_LOADING_UI_3}); - console.log(err.response.data); - reject(); + }) }) - }); - - } \ No newline at end of file From 445687cc54a847d1cc6a056fdc406fc66c6e5195 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Fri, 22 Nov 2019 18:21:52 -0500 Subject: [PATCH 16/22] Automatically display new Direct Messages --- twistter-frontend/src/pages/directMessages.js | 3 ++- twistter-frontend/src/redux/actions/dataActions.js | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index 63fb8b0..3f43882 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -207,7 +207,7 @@ export class directMessages extends Component { componentDidMount() { this.props.getDirectMessages(); - // this.updatePage(); + this.updatePage(); } // Updates the state whenever redux is updated @@ -230,6 +230,7 @@ export class directMessages extends Component { updatePage = async() => { while (true) { await this.sleep(15000); + // console.log("getting new DMs"); this.props.getNewDirectMessages(); } } diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js index d387b6a..595d2d4 100644 --- a/twistter-frontend/src/redux/actions/dataActions.js +++ b/twistter-frontend/src/redux/actions/dataActions.js @@ -12,6 +12,8 @@ import { } from '../types'; import axios from "axios"; +// TODO: Tidy up these functions. They shouldn't have all these promises in them. + export const getDirectMessages = () => (dispatch) => { dispatch({type: SET_LOADING_UI_2}); axios.get('/dms') @@ -37,6 +39,10 @@ export const getNewDirectMessages = () => (dispatch) => { dispatch({type: CLEAR_ERRORS}); resolve(); }) + .catch((err) => { + console.log(err) + reject(err); + }) }) } @@ -52,6 +58,10 @@ export const reloadDirectMessageChannels = () => (dispatch) => { dispatch({type: CLEAR_ERRORS}); resolve(); }) + .catch((err) => { + console.log(err) + reject(err); + }) }) } @@ -66,7 +76,7 @@ export const createNewDirectMessage = (username) => (dispatch) => { axios.post('/dms/new', data) .then((res) => { - console.log(res.data); + // console.log(res.data); if (res.data.err) { dispatch({ type: SET_ERRORS, @@ -90,7 +100,7 @@ export const createNewDirectMessage = (username) => (dispatch) => { }); dispatch({type: SET_NOT_LOADING_UI_3}); console.log(err.response.data); - reject(); + reject(err); }) }); } From 731e01c5527a560dcae132ff93e5bf995fa91d8a Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 27 Nov 2019 21:51:16 -0500 Subject: [PATCH 17/22] Comment out some lines in users.js that were throwing errors --- functions/handlers/users.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 9c13564..7b9a2e0 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -161,11 +161,13 @@ exports.login = (req, res) => { return; }) .catch(function(err) { - if (!doc.exists) { - return res - .status(403) - .json({ general: "Invalid credentials. Please try again." }); - } + // FIX: doc variable is out of scope + // if (!doc.exists) { + // return res + // .status(403) + // .json({ general: "Invalid credentials. Please try again." }); + // } + console.log(err); return res.status(500).send(err); }); } From f7ef0cb333ffffaaee77455b8b30cd310dcf55f7 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 27 Nov 2019 21:52:14 -0500 Subject: [PATCH 18/22] Fix DM message content overflowing --- twistter-frontend/package.json | 3 +- twistter-frontend/src/pages/directMessages.js | 144 ++++++++++++------ .../src/redux/actions/dataActions.js | 9 ++ 3 files changed, 108 insertions(+), 48 deletions(-) diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index a0ec11c..1e5742a 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -21,7 +21,8 @@ "react-scripts": "0.9.5", "redux": "^4.0.4", "redux-thunk": "^2.3.0", - "typeface-roboto": "0.0.75" + "typeface-roboto": "0.0.75", + "underscore": "^1.9.1" }, "devDependencies": {}, "scripts": { diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index 3f43882..6cdfd74 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; +import _ from "underscore"; // Material UI import Box from '@material-ui/core/Box'; @@ -75,24 +76,35 @@ const styles = { marginTop: 5 }, dmRecentMessageSelected: { + wordBreak: "break-all", color: '#D6D6D6' }, dmRecentMessageUnselected: { + wordBreak: "break-all", color: 'black' }, dmListItemContainer: { height: 100 }, + dmListLayoutContainer: { + height: "100%" + }, + dmListRecentMessage: { + marginLeft: 10, + marginRight: 10 + }, dmListTextLayout: { - height: '100%' + height: 30 }, dmCardUnselected: { fontSize: 20, - backgroundColor: '#FFFFFF' + backgroundColor: '#FFFFFF', + width: 300 }, dmCardSelected: { fontSize: 20, - backgroundColor: '#1da1f2' + backgroundColor: '#1da1f2', + width: 300 }, messagesGrid: { // // margin: "auto" @@ -124,7 +136,7 @@ const styles = { minWidth: 150, maxWidth: 350, minHeight: 40, - marginLeft: 2, + marginLeft: 15, marginTop: 2, marginBottom: 10, backgroundColor: '#008394', @@ -132,6 +144,9 @@ const styles = { float: 'left' }, messageContent: { + // maxWidth: 330, + // width: 330, + wordBreak: "break-all", textAlign: 'left', marginLeft: 5, marginRight: 5 @@ -207,12 +222,12 @@ export class directMessages extends Component { componentDidMount() { this.props.getDirectMessages(); - this.updatePage(); + // this.updatePage(); } // Updates the state whenever redux is updated componentWillReceiveProps(nextProps) { - if (nextProps.directMessages) { + if (nextProps.directMessages && !_.isEqual(nextProps.directMessages, this.state.dmData)) { this.setState({ dmData: nextProps.directMessages}, () => { if (this.state.selectedChannel) { this.state.dmData.forEach((channel) => { @@ -244,16 +259,22 @@ export class directMessages extends Component { this.setState({ hasChannelSelected: true }); + + const dmItemsUpper = document.getElementById("dmItemsUpper"); + let target = event.target; let dmChannelKey; - // Determine which DM channel was clicked by finding the key - // An if statement is needed because the user could click the card or the typography - if (event.target.parentNode.parentNode.parentNode.dataset.key === undefined) { - // They clicked text - dmChannelKey = event.target.parentNode.parentNode.parentNode.parentNode.dataset.key; - } else { - // They clicked the background/card - dmChannelKey = event.target.parentNode.parentNode.parentNode.dataset.key; + // Determine which DM channel was clicked by finding the data-key. + // A while loop is necessary, because the user can click on any part of the + // DM list item. dmItemsUpper is the list container of the dmItems + while (target !== dmItemsUpper) { + dmChannelKey = target.dataset.key; + + if (dmChannelKey) { + break; + } else { + target = target.parentNode; + } } // Save the entire DM channel in the state so that it is easier to load the messages @@ -275,6 +296,21 @@ export class directMessages extends Component { return dayjs(dateString).fromNow(); } + shortenText = (text, length) => { + // Shorten the text + let shortened = text.slice(0, length + 1); + + // Trim whitespace from the end of the text + if (shortened[shortened.length - 1] === ' ') { + shortened = shortened.trimRight(); + } + + // Add ... to the end + shortened = `${shortened}...`; + + return shortened; + } + handleOpenAddDMPopover = (event) => { this.setState({ anchorEl: event.currentTarget @@ -352,20 +388,41 @@ export class directMessages extends Component { } > - - - - - {channel.recipient} - + + + + + + + {channel.recipient} + + + + + {channel.recentMessageTimestamp ? ( + this.formatDateToTimeDiff(channel.recentMessageTimestamp) + ) : null} + + + + + - {channel.recentMessage ? channel.recentMessage : 'No messages'} - - - - 65 ? + this.shortenText(channel.recentMessage, 65) + : + channel.recentMessage } - > - {channel.recentMessageTimestamp ? ( - this.formatDateToTimeDiff(channel.recentMessageTimestamp) - ) : null} @@ -518,7 +568,7 @@ export class directMessages extends Component { - + {dmListMarkup} @@ -553,9 +603,9 @@ export class directMessages extends Component { className={classes.messageButton} onClick={this.handleClickSend} disabled={ - sendingDirectMessage - // !this.state.drafts[this.state.selectedChannel.dmId] || - // this.state.drafts[this.state.selectedChannel.dmId] !== "" + sendingDirectMessage || + !this.state.drafts[this.state.selectedChannel.dmId] || + this.state.drafts[this.state.selectedChannel.dmId] === "" } > diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js index 595d2d4..f6a63ba 100644 --- a/twistter-frontend/src/redux/actions/dataActions.js +++ b/twistter-frontend/src/redux/actions/dataActions.js @@ -25,6 +25,15 @@ export const getDirectMessages = () => (dispatch) => { dispatch({type: SET_NOT_LOADING_UI_2}); dispatch({type: CLEAR_ERRORS}); }) + .catch((err) => { + console.error(err); + dispatch({ + type: SET_ERRORS, + payload: { + errors: err.response.data.error + } + }); + }) } export const getNewDirectMessages = () => (dispatch) => { From 0d8850c45dee672d8ad733d8758aeb9e02c51014 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 27 Nov 2019 21:53:02 -0500 Subject: [PATCH 19/22] Comment out logging in fbAuth.js --- functions/util/fbAuth.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/functions/util/fbAuth.js b/functions/util/fbAuth.js index 35253e7..d440bb9 100644 --- a/functions/util/fbAuth.js +++ b/functions/util/fbAuth.js @@ -4,12 +4,12 @@ const { admin, db } = require('./admin'); // The function will only execute if the user is logged in, or rather, they have // a valid token module.exports = (req, res, next) => { - console.log(req); - console.log(req.body); - console.log(req.headers); - console.log(req.headers.authorization); - console.log(JSON.stringify(req.body)); - console.log(JSON.stringify(req.header)); + // console.log(req); + // console.log(req.body); + // console.log(req.headers); + // console.log(req.headers.authorization); + // console.log(JSON.stringify(req.body)); + // console.log(JSON.stringify(req.header)); let idToken; From fee222574516ac9453c5fca003193cad15d00797 Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 27 Nov 2019 23:06:25 -0500 Subject: [PATCH 20/22] Disable or enable Direct Messages --- functions/handlers/users.js | 16 +++++ functions/index.js | 4 ++ twistter-frontend/src/pages/directMessages.js | 6 +- twistter-frontend/src/pages/editProfile.js | 58 ++++++++++++++++++- 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 7b9a2e0..6ee0f0f 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -549,6 +549,22 @@ exports.getDirectMessages = (req, res) => { }); } +// Toggles direct messages on or off depending on the requese +/* Request Parameters + * enable: bool + */ +exports.toggleDirectMessages = (req, res) => { + const enable = req.body.enable; + const user = req.userData.handle; + db.doc(`/users/${user}`).update({dmEnabled: enable}) + .then(() => { + return res.status(201).json({message: "Success"}); + }) + .catch((err) => { + return res.status(500).json({error: err}); + }) +} + // Returns a promise that resolves if user has DMs enabled // and rejects if there is an error or DMs are disabled isDirectMessageEnabled = (username) => { diff --git a/functions/index.js b/functions/index.js index cea6dc2..9278e99 100644 --- a/functions/index.js +++ b/functions/index.js @@ -15,6 +15,7 @@ const { sendDirectMessage, createDirectMessage, checkDirectMessagesEnabled, + toggleDirectMessages, getUserDetails, getProfileInfo, login, @@ -50,6 +51,9 @@ app.post("/dms/new", fbAuth, createDirectMessage); // Checks if the user provided has DMs enabled or not app.post("/dms/enabled", checkDirectMessagesEnabled); +// Used to toggle DMs on or off for the current user +app.post("/dms/toggle", fbAuth, toggleDirectMessages); + app.get("/getUser", fbAuth, getUserDetails); // Returns all profile data of the currently logged in user diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js index 6cdfd74..eadadf9 100644 --- a/twistter-frontend/src/pages/directMessages.js +++ b/twistter-frontend/src/pages/directMessages.js @@ -366,7 +366,7 @@ export class directMessages extends Component { } render() { - const { classes } = this.props; + const { classes, user: { credentials: { dmEnabled } } } = this.props; const loadingDirectMessages = this.props.UI.loading2; const creatingDirectMessage = this.props.UI.loading3; const sendingDirectMessage = this.props.UI.loading4; @@ -563,7 +563,8 @@ export class directMessages extends Component { ); return ( - loadingDirectMessages ? : + loadingDirectMessages ? : + (dmEnabled !== undefined && dmEnabled !== null && !dmEnabled ? Oops! It looks like you have DMs disabled. You can enable them on the Edit Profile page. : @@ -624,6 +625,7 @@ export class directMessages extends Component { + ) ); } } diff --git a/twistter-frontend/src/pages/editProfile.js b/twistter-frontend/src/pages/editProfile.js index 7272e76..dbc1adf 100644 --- a/twistter-frontend/src/pages/editProfile.js +++ b/twistter-frontend/src/pages/editProfile.js @@ -3,6 +3,7 @@ import axios from "axios"; import PropTypes from "prop-types"; // TODO: Add a read-only '@' in the left side of the handle input // TODO: Add a cancel button, that takes the user back to their profile page +// FIX: Empty bio does not update the database // Material-UI stuff import Button from "@material-ui/core/Button"; @@ -12,6 +13,11 @@ import Grid from "@material-ui/core/Grid"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import withStyles from "@material-ui/core/styles/withStyles"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Switch from "@material-ui/core/Switch"; + +// Redux +import { connect } from 'react-redux'; const styles = { form: { @@ -46,7 +52,8 @@ export class edit extends Component { lastName: res.data.lastName, email: res.data.email, handle: res.data.handle, - bio: res.data.bio + bio: res.data.bio, + dmEnabled: res.data.dmEnabled === false ? false : true }); }) .catch((err) => { @@ -69,6 +76,8 @@ export class edit extends Component { email: "", handle: "", bio: "", + dmEnabled: false, + togglingDirectMessages: false, loading: false, errors: {} }; @@ -129,6 +138,28 @@ export class edit extends Component { }); }; + handleDMSwitch = () => { + let enable; + + if (this.state.dmEnabled) { + enable = {enable: false}; + } else { + enable = {enable: true}; + } + + this.setState({ + dmEnabled: enable.enable, + togglingDirectMessages: true + }); + + axios.post("/dms/toggle", enable) + .then(() => { + this.setState({ + togglingDirectMessages: false + }); + }) + } + render() { const { classes } = this.props; const { errors, loading } = this.state; @@ -215,6 +246,18 @@ export class edit extends Component { onChange={this.handleChange} fullWidth /> + + } + label="Enable Direct Messages" + /> +

)} + {authenticated && ( + + )} {!authenticated && ( )} {authenticated && ( - )} diff --git a/twistter-frontend/src/pages/editProfile.js b/twistter-frontend/src/pages/editProfile.js index 27db7a1..1d1e3cc 100644 --- a/twistter-frontend/src/pages/editProfile.js +++ b/twistter-frontend/src/pages/editProfile.js @@ -283,122 +283,11 @@ export class editProfile extends Component { const id = open ? 'simple-popover' : undefined; return ( -// <<<<<<< dms -// -// -// -// -// Edit Profile -// -//
-// -// -// -// -// -// -// -// -// -// -// -// -// } -// label="Enable Direct Messages" -// /> -//

-// -//
-// ======= this.state.pageLoading ? : -// >>>>>>> master