diff --git a/twistter-frontend/src/pages/directMessages.js b/twistter-frontend/src/pages/directMessages.js
new file mode 100644
index 0000000..40f08fb
--- /dev/null
+++ b/twistter-frontend/src/pages/directMessages.js
@@ -0,0 +1,677 @@
+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';
+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';
+
+// 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 {
+ getDirectMessages,
+ createNewDirectMessage,
+ getNewDirectMessages,
+ reloadDirectMessageChannels,
+ sendDirectMessage
+} from '../redux/actions/dataActions';
+
+const styles = {
+ pageContainer: {
+ minHeight: 'calc(100vh - 50px - 60px)'
+ },
+ sidePadding: {
+ maxWidth: 350
+ },
+ dmList: {
+ width: 300,
+ 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'
+ },
+ 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: {
+ wordBreak: "break-all",
+ color: '#D6D6D6'
+ },
+ dmRecentMessageUnselected: {
+ wordBreak: "break-all",
+ color: 'black'
+ },
+ dmRecentMessageDisabled: {
+ wordBreak: "break-all",
+ color: 'red'
+ },
+ dmListItemContainer: {
+ height: 100
+ },
+ dmListLayoutContainer: {
+ height: "100%"
+ },
+ dmListRecentMessage: {
+ marginLeft: 10,
+ marginRight: 10
+ },
+ dmListTextLayout: {
+ height: 30
+ },
+ dmCardUnselected: {
+ fontSize: 20,
+ backgroundColor: '#FFFFFF',
+ width: 300
+ },
+ dmCardSelected: {
+ fontSize: 20,
+ backgroundColor: '#1da1f2',
+ width: 300
+ },
+ 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: 15,
+ marginTop: 2,
+ marginBottom: 10,
+ backgroundColor: '#008394',
+ color: '#FFFFFF',
+ float: 'left'
+ },
+ messageContent: {
+ // maxWidth: 330,
+ // width: 330,
+ wordBreak: "break-all",
+ 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
+ },
+ loadingUsernameChecks: {
+ height: 55,
+ width: 55,
+ marginLeft: 5
+ },
+ errorIcon: {
+ height: 55,
+ width: 55,
+ marginLeft: 5,
+ color: '#ff3d00'
+ },
+ checkMarkIcon: {
+ height: 55,
+ width: 55,
+ marginLeft: 5,
+ color: '#1da1f2'
+ },
+ createButton: {
+ // textAlign: "center",
+ // display: "block",
+ marginLeft: 96,
+ marginRight: 96,
+ position: "relative"
+ }
+};
+
+export class directMessages extends Component {
+ constructor() {
+ super();
+ this.state = {
+ hasChannelSelected: false,
+ selectedChannel: null,
+ dmData: null,
+ anchorEl: null,
+ createDMUsername: '',
+ usernameValid: false,
+ // message: '',
+ drafts: {},
+ errors: null
+ };
+ }
+
+ componentDidUpdate() {
+ if (this.state.hasChannelSelected) {
+ document.getElementById('messagesContainer').scrollTop = document.getElementById(
+ 'messagesContainer'
+ ).scrollHeight;
+ }
+ }
+
+ componentDidMount() {
+ this.props.getDirectMessages();
+ // this.updatePage();
+ }
+
+ // Updates the state whenever redux is updated
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.directMessages && !_.isEqual(nextProps.directMessages, this.state.dmData)) {
+ 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
+ });
+ }
+ });
+ }
+ });
+ }
+ }
+
+ updatePage = async() => {
+ while (true) {
+ await this.sleep(15000);
+ // console.log("getting new DMs");
+ this.props.getNewDirectMessages();
+ }
+ }
+
+ sleep = (ms) => {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ // Handles selecting different DM channels
+ handleClickChannel = (event) => {
+ this.setState({
+ hasChannelSelected: true
+ });
+
+ const dmItemsUpper = document.getElementById("dmItemsUpper");
+ let target = event.target;
+ let dmChannelKey;
+
+ // 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
+ this.state.dmData.forEach((channel) => {
+ if (channel.dmId === dmChannelKey) {
+ this.setState({
+ selectedChannel: channel
+ });
+ }
+ });
+ };
+
+ formatDateToString(dateString) {
+ let newDate = new Date(Date.parse(dateString));
+ return newDate.toDateString();
+ }
+
+ formatDateToTimeDiff(dateString) {
+ 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
+ });
+ };
+
+ handleCloseAddDMPopover = () => {
+ this.setState({
+ anchorEl: null,
+ createDMUsername: '',
+ usernameValid: false
+ });
+ };
+
+ handleChangeAddDMUsername = (event) => {
+ this.setState({
+ createDMUsername: event.target.value
+ });
+ };
+
+ handleClickCreate = () => {
+ this.props.createNewDirectMessage(this.state.createDMUsername)
+ .then(() => {
+ return this.props.reloadDirectMessageChannels();
+ })
+ .then(() => {
+ this.handleCloseAddDMPopover();
+ return;
+ })
+ .catch(() => {
+ return;
+ })
+ }
+
+ 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, user: { credentials: { dmEnabled } } } = 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
+ const open = Boolean(this.state.anchorEl);
+ const id = open ? 'simple-popover' : undefined;
+
+ 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 ? ( + this.state.selectedChannel.messages.map((messageObj) => ( +No DMs here
+ ) : ( +Select a DM channel
+ ); + + let addDMMarkup = ( +