Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications #44

Merged
merged 9 commits into from
Jul 20, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"axios": "^0.19.2",
"babel-loader": "^8.1.0",
"dotenv": "^8.2.0",
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-google-login": "^5.1.20",
Expand Down
2 changes: 1 addition & 1 deletion src/components/AddOffering/AddOffering.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class AddOffering extends React.Component {
this.state.comment,
this.state.addedItems,
this.props.currentUser,
this.props.itemDetail.id,
this.props.itemDetail.id
);

let id = this.props.itemDetail.id;
Expand Down
26 changes: 2 additions & 24 deletions src/components/Navigation/NavBar/ActionBtns/ActionButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { connect } from "react-redux";

import Button from "@material-ui/core/Button";
import { makeStyles } from "@material-ui/core/styles";
import { IconButton } from "@material-ui/core";
import AddCircleOutlineOutlinedIcon from "@material-ui/icons/AddCircleOutlineOutlined";
import NotificationsNoneOutlinedIcon from "@material-ui/icons/NotificationsNoneOutlined";
import NotificationsMenu from "./NotificationsMenu";

import { unsetUser } from "../../../../redux/actions/userActions";
import {
openPostingModal,
openLoginModal,
Expand Down Expand Up @@ -40,10 +38,6 @@ const loggedInStyles = makeStyles(() => ({
fontSize: "1.3rem",
fontWeight: "100",
},
notificationBtn: {
fontSize: "1.7rem",
padding: "5px",
},
accountBtn: {
fontSize: "2rem",
padding: "5px",
Expand Down Expand Up @@ -90,18 +84,6 @@ const LoginBtn = (props) => {
);
};

const NotificationBtn = (props) => {
return (
<IconButton
className={props.iconBtnClass}
color="inherit"
aria-label="chat"
>
<NotificationsNoneOutlinedIcon className={props.notificationBtnClass} />
</IconButton>
);
};

/**
* MAIN: Logged Out Action Item Components
**/
Expand All @@ -116,10 +98,7 @@ const LoggedInActionItems = (props) => {
openPostingModal={props.openPostingModal}
/>
<AppBarDivider className={classes.divider} />
<NotificationBtn
iconBtnClass={classes.iconBtn}
notificationBtnClass={classes.notificationBtn}
/>
<NotificationsMenu />
<AccountBtn
iconBtnClass={classes.iconBtn}
accountBtnClass={classes.accountBtn}
Expand All @@ -138,5 +117,4 @@ const ActionItems = (props) => {
export default connect(null, {
openPostingModal,
openLoginModal,
unsetUser,
})(ActionItems);
155 changes: 155 additions & 0 deletions src/components/Navigation/NavBar/ActionBtns/Notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { ListItemAvatar, ListItemText } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import LocalOfferIcon from "@material-ui/icons/LocalOffer";
import CheckCircleIcon from "@material-ui/icons/CheckCircle";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import moment from "moment";
import DeleteIcon from "@material-ui/icons/Delete";
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
import CancelIcon from "@material-ui/icons/Cancel";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import CheckBoxIcon from "@material-ui/icons/CheckBox";
import MarkunreadIcon from "@material-ui/icons/Markunread";
import Button from "@material-ui/core/Button";
import {
updateNotificationAsync,
removeNotificationAsync,
} from "../../../../redux/actions/notificationActions";

const useStyles = makeStyles(() => ({
acceptedIcon: {
color: "green",
},
rejectedIcon: {
color: "red",
},
moreItems: {
textTransform: "none",
},
}));

const SeeMoreMenu = ({
dispatch,
handleDeleteClick,
onUpdateStatus,
_id,
isRead,
}) => {
const [anchorEl, setAnchorEl] = useState(null);
const classes = useStyles();
const open = Boolean(anchorEl);

const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleReadClick = () => {
dispatch(updateNotificationAsync(_id, !isRead)).then(() => {
onUpdateStatus(_id, !isRead);
});
};

return (
<div>
<IconButton
aria-label="more"
aria-controls="more-menu"
aria-haspopup="true"
onClick={handleClick}
>
<MoreHorizIcon />
</IconButton>
<Menu
id="more-menu"
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
keepMounted
open={open}
onClose={handleClose}
>
<MenuItem onClick={handleClose}>
<Button
color="inherit"
onClick={handleReadClick}
className={classes.moreItems}
startIcon={isRead ? <MarkunreadIcon /> : <CheckBoxIcon />}
>
{isRead ? "Mark as unread" : "Mark as read"}
</Button>
</MenuItem>
<MenuItem onClick={handleClose}>
<Button
color="inherit"
onClick={handleDeleteClick}
className={classes.moreItems}
startIcon={<DeleteIcon />}
>
Delete
</Button>
</MenuItem>
</Menu>
</div>
);
};

const Notification = ({
dispatch,
_id,
type,
date,
isRead,
content,
key,
onUpdateStatus,
}) => {
const classes = useStyles();

const [isExisting, setIsExisting] = useState(true);

const handleDeleteClick = () => {
dispatch(removeNotificationAsync(_id)).then(() => {
setIsExisting(false);
});
};

return isExisting ? (
<MenuItem selected={!isRead} divider={true} key={key}>
<ListItemAvatar>
{type === "newOffering" ? (
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
<LocalOfferIcon color="primary" />
) : type === "offeringAccepted" ? (
<CheckCircleIcon className={classes.acceptedIcon} />
) : (
<CancelIcon className={classes.rejectedIcon} />
)}
</ListItemAvatar>
<ListItemText
primary={content}
secondary={moment(date).format("MMMM Do YYYY, h:mm a")}
/>
<SeeMoreMenu
dispatch={dispatch}
handleDeleteClick={handleDeleteClick}
onUpdateStatus={onUpdateStatus}
_id={_id}
isRead={isRead}
/>
</MenuItem>
) : null;
};

export default connect()(Notification);
165 changes: 165 additions & 0 deletions src/components/Navigation/NavBar/ActionBtns/NotificationsMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { makeStyles } from "@material-ui/core/styles";
import { IconButton, List } from "@material-ui/core";
import NotificationsNoneOutlinedIcon from "@material-ui/icons/NotificationsNoneOutlined";
import Divider from "@material-ui/core/Divider";
import Button from "@material-ui/core/Button";
import Menu from "@material-ui/core/Menu";

import Notification from "./Notification";
import {
updateAllNotificationsAsync,
removeAllNotificationsAsync,
getNotificationsAsync,
} from "../../../../redux/actions/notificationActions";

const useStyles = makeStyles(() => ({
notificationsMenu: {
border: "1rem",
borderColor: "black",
},
notificationBtn: {
fontSize: "1.7rem",
padding: "5px",
},
notificationsList: {
width: "25rem",
textOverflow: "auto",
maxHeight: 300,
overflow: "auto",
},
headingBtn: {
textTransform: "none",
},
headingBtns: {
display: "flex",
justifyContent: "space-between",
},
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
}));

const NotificationsMenu = ({ dispatch, notifications, currentUser }) => {
const classes = useStyles();
const [anchorEl, setAnchorEl] = useState(null);
const [markAllStatus, setmarkAllStatus] = useState(true);
const [myNotifications, setMyNotifications] = useState(notifications);

useEffect(() => {
retrieveNotifications();
}, [markAllStatus]);

const handleOpenNotifications = (event) => {
setAnchorEl(event.currentTarget);
retrieveNotifications();
};

const retrieveNotifications = () => {
dispatch(getNotificationsAsync(currentUser.user._id)).then((res) => {
setMyNotifications(res.notifications);
});
};

const handleClickMarkAllAsRead = () => {
if (notifications.length > 0) {
dispatch(
updateAllNotificationsAsync(currentUser.user._id, markAllStatus)
).then(() => {
setmarkAllStatus(!markAllStatus);
});
}
};

const handleClickDeleteAll = () => {
dispatch(removeAllNotificationsAsync(currentUser.user._id)).then(() => {
retrieveNotifications();
});
};

const handleClose = () => {
setAnchorEl(null);
};

const updateIndividualReadStatus = (_id, newReadStatus) => {
const updatedNotifications = myNotifications.map((notification) => {
if (notification._id === _id) {
notification.isRead = newReadStatus;
}

return notification;
});
setMyNotifications(updatedNotifications);
};

const renderNotifications = (myNotifications) => {
return myNotifications.map((notification) => {
return (
<Notification
key={notification._id}
_id={notification._id}
userId={notification.userId}
type={notification.type}
isRead={notification.isRead}
date={notification.date}
content={notification.content}
onUpdateStatus={updateIndividualReadStatus}
/>
);
});
};

const open = Boolean(anchorEl);

return (
<div>
<IconButton
color="inherit"
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
aria-label="notifications"
aria-controls="notifications-menu"
aria-haspopup="true"
onClick={handleOpenNotifications}
>
<NotificationsNoneOutlinedIcon className={classes.notificationBtn} />
</IconButton>
<Menu
getContentAnchorEl={null}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
}}
id="notifications-menu"
anchorEl={anchorEl}
keepMounted
open={open}
className={classes.notificationsMenu}
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
onClose={handleClose}
>
<div className={classes.headingBtns}>
<Button
className={classes.headingBtn}
onClick={handleClickMarkAllAsRead}
>
{`Mark all as ${markAllStatus ? "read" : "unread"}`}
</Button>
<Button className={classes.headingBtn} onClick={handleClickDeleteAll}>
Delete all
abid-salahi marked this conversation as resolved.
Show resolved Hide resolved
</Button>
</div>
<Divider />
<List className={classes.notificationsList}>
{renderNotifications(myNotifications)}
</List>
</Menu>
</div>
);
};

const mapStateToProps = (state) => ({
notifications: state.notifications,
currentUser: state.currentUser,
});

export default connect(mapStateToProps)(NotificationsMenu);
Loading