diff --git a/client/src/components/AppBanner.js b/client/src/components/AppBanner.js
index 1fefad0..029d305 100644
--- a/client/src/components/AppBanner.js
+++ b/client/src/components/AppBanner.js
@@ -1,5 +1,5 @@
import { useContext, useState } from 'react';
-import { Link } from 'react-router-dom'
+import { Link } from 'react-router-dom';
import AuthContext from '../auth';
import { GlobalStoreContext } from '../store'
import AppBar from '@mui/material/AppBar';
@@ -17,13 +17,8 @@ export default function AppBanner() {
const [anchorEl, setAnchorEl] = useState(null);
const isMenuOpen = Boolean(anchorEl);
- const handleProfileMenuOpen = (event) => {
- setAnchorEl(event.currentTarget);
- };
-
- const handleMenuClose = () => {
- setAnchorEl(null);
- };
+ const handleProfileMenuOpen = (event) => { setAnchorEl(event.currentTarget); };
+ const handleMenuClose = () => { setAnchorEl(null); };
const handleLogout = () => {
handleMenuClose();
diff --git a/client/src/components/ListCard.js b/client/src/components/ListCard.js
index 2f38dab..f223695 100644
--- a/client/src/components/ListCard.js
+++ b/client/src/components/ListCard.js
@@ -10,7 +10,9 @@ import DeleteIcon from '@mui/icons-material/Delete';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ThumbDownIcon from '@mui/icons-material/ThumbDown';
+import ThumbDownOutlinedIcon from '@mui/icons-material/ThumbDownOutlined';
import ThumbUpIcon from '@mui/icons-material/ThumbUp';
+import ThumbUpOutlinedIcon from '@mui/icons-material/ThumbUpOutlined';
/**
This is a card in our list of top 5 lists. It lets select
@@ -38,6 +40,10 @@ function ListCard(props) {
event.target.value = "";
}
}
+ function handleRate(event, type) {
+ event.stopPropagation();
+ store.rate(listInfo._id, type);
+ }
async function handleDeleteList(event, id) {
event.stopPropagation();
@@ -47,28 +53,54 @@ function ListCard(props) {
let text = "", likes = "", dislikes = "", views = "", comment = "";
if (listInfo.published) {
text = Published: {listInfo.publishedDate}
- likes =
-
- {listInfo.likes.length}
- ;
- dislikes =
+ likes = listInfo.likes.includes(auth.user.email) ?
+ handleRate(event, "like")}
+ >
+ {listInfo.likes.length}
+
+ :
+ handleRate(event, "like")}
+ >
+ {listInfo.likes.length}
+
+ dislikes = listInfo.dislikes.includes(auth.user.email) ?
handleRate(event, "dislike")}
>
{listInfo.dislikes.length}
- ;
+
+ :
+ handleRate(event, "dislike")}
+ >
+ {listInfo.dislikes.length}
+
views = Views: {listInfo.views};
comment =
{ handleDeleteList(event, listInfo._id) }}
aria-label= 'delete'
- sx={{ "grid-area": "1 / 3 / 2 / 4" }}
+ sx={{ gridArea: "1 / 3 / 2 / 4" }}
>
{views}
-
+
@@ -162,7 +194,7 @@ function ListCard(props) {
"grid-row-gap": "0px",
width: "100%"
}}>
-
+
{listInfo.name}
By: {listInfo.ownerName}
@@ -170,7 +202,7 @@ function ListCard(props) {
color: "#d4af37",
backgroundColor: "#2c2f70",
width: "97%",
- "grid-area": "2 / 1 / 3 / 2",
+ gridArea: "2 / 1 / 3 / 2",
borderRadius: "10px",
fontSize: "24pt",
fontWeight: "bold"
@@ -184,32 +216,32 @@ function ListCard(props) {
{text}
{likes}
{dislikes}
{ handleDeleteList(event, listInfo._id) }}
aria-label= 'delete'
- sx={{ "grid-area": "1 / 5 / 2 / 6" }}
+ sx={{ gridArea: "1 / 5 / 2 / 6" }}
>
-
+
{views}
+
}
diff --git a/client/src/components/Toolbar.js b/client/src/components/Toolbar.js
index 562ba3c..8440b13 100644
--- a/client/src/components/Toolbar.js
+++ b/client/src/components/Toolbar.js
@@ -1,6 +1,7 @@
-import { useContext } from 'react';
+import { useState, useContext } from 'react';
import { GlobalStoreContext } from '../store/index.js';
-import { Box, IconButton } from '@mui/material';
+import AuthContext from '../auth';
+import { Box, IconButton, Menu, MenuItem } from '@mui/material';
import HomeIcon from '@mui/icons-material/Home';
import GroupIcon from '@mui/icons-material/Groups';
import PersonIcon from '@mui/icons-material/Person';
@@ -8,9 +9,42 @@ import FunctionsIcon from '@mui/icons-material/Functions';
import SortIcon from '@mui/icons-material/Sort';
function Toolbar() {
+ const { auth } = useContext(AuthContext);
const { store } = useContext(GlobalStoreContext);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const isMenuOpen = Boolean(anchorEl);
function handleChangeView(view) { store.setView(view); }
+ function handleSort(type) { store.sortBy(type); }
+ function handleSearch(event) {
+ if (event.code === "Enter") {
+ if (event.target.value === "") {
+ store.loadListData();
+ } else {
+ let filter = {};
+ switch (store.view) {
+ case "home":
+ filter = { ownerEmail: auth.user.email, name: { $regex: '^' + event.target.value, $options: 'i' } }
+ break;
+ case "all":
+ filter = { published: true, name: { $regex: '^' + event.target.value + '$', $options: 'i' } }
+ break;
+ case "user":
+ filter = { published: true, ownerName: { $regex: '^' + event.target.value + '$', $options: 'i' } }
+ break;
+ case "community":
+ filter = { ownerEmail: "community", name: { $regex: '^' + event.target.value + '$', $options: 'i' } }
+ break;
+ default:
+ return;
+ }
+ store.loadListData(filter);
+ }
+ }
+ }
+
+ const handleMenuOpen = (event) => { setAnchorEl(event.currentTarget); }
+ const handleMenuClose = () => { setAnchorEl(null); }
return (
)
}
diff --git a/client/src/components/WorkspaceScreen.js b/client/src/components/WorkspaceScreen.js
index 2d28fcf..e93652f 100644
--- a/client/src/components/WorkspaceScreen.js
+++ b/client/src/components/WorkspaceScreen.js
@@ -1,4 +1,4 @@
-import { useContext, useEffect } from 'react';
+import { useContext } from 'react';
import Top5Item from './Top5Item.js';
import { GlobalStoreContext } from '../store/index.js';
/**
@@ -21,7 +21,14 @@ function WorkspaceScreen() {
store.closeCurrentList();
}
function handlePublish() {
- store.publishCurrentList();
+ if (new Set(newItems).size !== newItems.length) { return; }
+ for (const item of newItems) {
+ if (item === "" || !/[a-z0-9]/i.test(item.charAt(0))) { return; }
+ }
+ let id = store.currentList._id;
+ store.changeListName(id, newName);
+ store.changeListItems(id, newItems);
+ store.publishList(id);
store.closeCurrentList();
}
diff --git a/client/src/store/index.js b/client/src/store/index.js
index 3799b00..61bee49 100644
--- a/client/src/store/index.js
+++ b/client/src/store/index.js
@@ -21,7 +21,8 @@ export const GlobalStoreActionType = {
UNMARK_LIST_FOR_DELETION: "UNMARK_LIST_FOR_DELETION",
SET_CURRENT_LIST: "SET_CURRENT_LIST",
SET_OPENED_LIST: "SET_OPENED_LIST",
- CHANGE_VIEW: "CHANGE_VIEW"
+ CHANGE_VIEW: "CHANGE_VIEW",
+ SORT_LIST_DATA: "SORT_LIST_DATA"
}
// WITH THIS WE'RE MAKING OUR GLOBAL DATA STORE
@@ -84,7 +85,7 @@ function GlobalStoreContextProvider(props) {
return setStore({
listData: payload,
currentList: null,
- openedList: null,
+ openedList: store.openedList,
newListCounter: store.newListCounter,
listMarkedForDeletion: null,
view: store.view
@@ -145,6 +146,17 @@ function GlobalStoreContextProvider(props) {
view: payload
});
}
+ // SORT BY
+ case GlobalStoreActionType.SORT_LIST_DATA: {
+ return setStore({
+ listData: store.listData.sort(payload),
+ currentList: null,
+ openedList: store.openedList,
+ newListCounter: store.newListCounter,
+ listMarkedForDeletion: null,
+ view: store.view
+ })
+ }
default:
return store;
}
@@ -163,19 +175,7 @@ function GlobalStoreContextProvider(props) {
top5List.name = newName;
async function updateList(top5List) {
response = await api.updateTop5ListById(top5List._id, top5List);
- if (response.data.success) {
- async function getListData() {
- response = await api.getTop5Lists({ ownerEmail: auth.user.email });
- if (response.data.success) {
- let dataArray = response.data.data;
- storeReducer({
- type: GlobalStoreActionType.UPDATE_LIST_DATA,
- payload: { listData: dataArray }
- });
- }
- }
- getListData();
- }
+ if (response.data.success) { store.loadListData(); }
}
updateList(top5List);
}
@@ -188,75 +188,137 @@ function GlobalStoreContextProvider(props) {
top5List.items = items;
async function updateList(top5List) {
response = await api.updateTop5ListById(top5List._id, top5List);
- if (response.data.success) {
- async function getListData() {
- response = await api.getTop5Lists();
- if (response.data.success) {
- let dataArray = response.data.data;
- storeReducer({
- type: GlobalStoreActionType.UPDATE_LIST_DATA,
- payload: { listData: dataArray }
- });
- }
- }
- getListData();
- }
+ if (response.data.success) { store.loadListData(); }
}
updateList(top5List);
}
}
- store.publishCurrentList = async function () {
- let top5List = store.currentList;
- if (auth.user.email !== top5List.ownerEmail) { return; }
- top5List.published = true;
- let today = new Date();
- const monthNames = ["January", "February", "March", "April", "May", "June",
- "July", "August", "September", "October", "November", "December"];
- top5List.publishedDate = monthNames[today.getMonth()] + " " + + today.getDate() + ", " + today.getFullYear();
- console.log(top5List.publishedDate);
- async function updateList(top5List) {
- let response = await api.updateTop5ListById(top5List._id, top5List);
- if (response.data.success) {
- async function getListData() {
- response = await api.getTop5Lists();
- if (response.data.success) {
- let dataArray = response.data.data;
- storeReducer({
- type: GlobalStoreActionType.UPDATE_LIST_DATA,
- payload: { listData: dataArray }
- });
- }
- }
- getListData();
+ store.publishList = async function (id) {
+ let response = await api.getTop5ListById(id);
+ if (response.data.success) {
+ let top5List = response.data.top5List;
+ if (auth.user.email !== top5List.ownerEmail) { return; }
+ top5List.published = true;
+ let today = new Date();
+ const monthNames = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"];
+ top5List.publishedDate = monthNames[today.getMonth()] + " " + + today.getDate() + ", " + today.getFullYear();
+ async function updateList(top5List) {
+ let response = await api.updateTop5ListById(top5List._id, top5List);
+ if (response.data.success) { store.loadListData(); }
}
- }
- updateList(top5List);
+ updateList(top5List);
+ }
}
store.comment = async function (user, body) {
let response = await api.getTop5ListById(store.openedList._id)
if (response.data.success) {
let top5List = response.data.top5List;
- top5List.comments.push({name: user, body: body});
+ top5List.comments.unshift({name: user, body: body});
async function updateList(top5List) {
- console.log(top5List);
response = await api.updateTop5ListById(top5List._id, top5List);
- if (response.data.success) {
- async function getListData() {
- response = await api.getTop5Lists();
- if (response.data.success) {
- let dataArray = response.data.data;
- storeReducer({
- type: GlobalStoreActionType.UPDATE_LIST_DATA,
- payload: { listData: dataArray }
- });
- }
- }
- getListData();
+ if (response.data.success) { store.loadListData(); }
+ }
+ updateList(top5List);
+ }
+ }
+ store.rate = async function (id, rating) {
+ let response = await api.getTop5ListById(id);
+ if (response.data.success) {
+ let top5List = response.data.top5List;
+ let likes = top5List.likes;
+ let dislikes = top5List.dislikes;
+ let email = auth.user.email;
+ if (rating === "like") {
+ if (likes.includes(email)) {
+ likes.splice(likes.indexOf(email), 1);
+ } else {
+ likes.push(email);
+ if (dislikes.includes(email)) { dislikes.splice(dislikes.indexOf(email), 1); }
+ }
+ } else if (rating === "dislike") {
+ if (dislikes.includes(email)) {
+ dislikes.splice(dislikes.indexOf(email), 1);
+ } else {
+ dislikes.push(email);
+ if (likes.includes(email)) { likes.splice(likes.indexOf(email), 1); }
}
+ } else {
+ return;
+ }
+ async function updateList(top5List) {
+ response = await api.updateTop5ListById(top5List._id, top5List);
+ if (response.data.success) { store.loadListData(); }
}
updateList(top5List);
}
}
+ store.sortBy = function (type) {
+ let convertDate = function (date) {
+ date = date.split(" ");
+ const monthNames = [null, "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"];
+ let day = date[1].substring(0, date[1].length - 1);
+ if (day.length === 1) { day = "0" + day; }
+ let month = monthNames.indexOf(date[0]);
+ if (month.length === 1) { month = "0" + month; }
+ return date[2] + month + day;
+ }
+ switch (type) {
+ case "new":
+ storeReducer({
+ type: GlobalStoreActionType.SORT_LIST_DATA,
+ payload: (a, b) => {
+ if (!a.published) { return 1; }
+ if (!b.published) { return -1; }
+ return convertDate(b.publishedDate) - convertDate(a.publishedDate);
+ }
+ });
+ break;
+ case "old":
+ storeReducer({
+ type: GlobalStoreActionType.SORT_LIST_DATA,
+ payload: (a, b) => {
+ if (!a.published) { return 1; }
+ if (!b.published) { return -1; }
+ return convertDate(a.publishedDate) - convertDate(b.publishedDate);
+ }
+ });
+ break;
+ case "views":
+ storeReducer({
+ type: GlobalStoreActionType.SORT_LIST_DATA,
+ payload: (a, b) => {
+ if (!a.published) { return 1; }
+ if (!b.published) { return -1; }
+ return b.views - a.views;
+ }
+ });
+ break;
+ case "likes":
+ storeReducer({
+ type: GlobalStoreActionType.SORT_LIST_DATA,
+ payload: (a, b) => {
+ if (!a.published) { return 1; }
+ if (!b.published) { return -1; }
+ return b.likes.length - a.likes.length;
+ }
+ });
+ break;
+ case "dislikes":
+ storeReducer({
+ type: GlobalStoreActionType.SORT_LIST_DATA,
+ payload: (a, b) => {
+ if (!a.published) { return 1; }
+ if (!b.published) { return -1; }
+ return b.dislikes.length - a.dislikes.length;
+ }
+ });
+ break;
+ default:
+ return;
+ }
+ }
// THIS FUNCTION PROCESSES CLOSING THE CURRENTLY LOADED LIST
store.closeCurrentList = function () {
@@ -280,7 +342,8 @@ function GlobalStoreContextProvider(props) {
likes: [],
dislikes: [],
views: 0,
- comments: []
+ comments: [],
+ communityPoints: []
};
const response = await api.createTop5List(payload);
if (response.data.success) {
@@ -288,8 +351,7 @@ function GlobalStoreContextProvider(props) {
storeReducer({
type: GlobalStoreActionType.CREATE_NEW_LIST,
payload: newList
- }
- );
+ });
// IF IT'S A VALID LIST THEN LET'S START EDITING IT
history.push("/top5list/" + newList._id);
@@ -387,10 +449,51 @@ function GlobalStoreContextProvider(props) {
} else {
let response = await api.getTop5ListById(id);
if (response.data.success) {
- storeReducer({
- type: GlobalStoreActionType.SET_OPENED_LIST,
- payload: response.data.top5List
- });
+ let top5List = response.data.top5List;
+ if (top5List.published) {
+ top5List.views = top5List.views + 1;
+ async function updateList(top5List) {
+ response = await api.updateTop5ListById(id, top5List);
+ if (response.data.success) {
+ let filter = {};
+ switch (store.view) {
+ case "home":
+ filter = { ownerEmail: auth.user.email }
+ break;
+ case "all":
+ filter = { published: true }
+ break;
+ case "user":
+ filter = { published: true }
+ break;
+ case "community":
+ filter = { ownerEmail: "community" }
+ break;
+ default:
+ return;
+ }
+ const response = await api.getTop5Lists(filter);
+ if (response.data.success) {
+ setStore({
+ listData: response.data.data,
+ currentList: null,
+ openedList: top5List,
+ newListCounter: store.newListCounter,
+ listMarkedForDeletion: null,
+ view: store.view
+ });
+ } else {
+ console.log("API FAILED TO GET THE LISTS");
+ }
+ }
+ }
+ updateList(top5List);
+ } else {
+ storeReducer({
+ type: GlobalStoreActionType.SET_OPENED_LIST,
+ payload: response.data.top5List
+ });
+ }
}
}
}
diff --git a/server/controllers/top5list-controller.js b/server/controllers/top5list-controller.js
index ae65772..e1f46fc 100644
--- a/server/controllers/top5list-controller.js
+++ b/server/controllers/top5list-controller.js
@@ -63,6 +63,7 @@ updateTop5List = async (req, res) => {
return res.status(200).json({
success: true,
id: top5List._id,
+ top5List: top5List,
message: 'Top 5 List updated!',
})
})
@@ -99,6 +100,8 @@ getTop5ListById = async (req, res) => {
}).catch(err => console.log(err))
}
getTop5Lists = async (req, res) => {
+ if (req.query.name) { req.query.name = JSON.parse(req.query.name); }
+ if (req.query.ownerName) { req.query.ownerName = JSON.parse(req.query.ownerName); }
await Top5List.find(req.query, (err, top5Lists) => {
if (err) { return res.status(400).json({ success: false, error: err }) }
return res.status(200).json({ success: true, data: top5Lists })
diff --git a/server/models/top5list-model.js b/server/models/top5list-model.js
index ff84242..e24710e 100644
--- a/server/models/top5list-model.js
+++ b/server/models/top5list-model.js
@@ -12,7 +12,8 @@ const Top5ListSchema = new Schema(
likes: { type: [String], required: true },
dislikes: { type: [String], required: true },
views: { type: Number, required: true },
- comments: { type: [{ name: String, body: String }], required: true }
+ comments: { type: [{ name: String, body: String }], required: true },
+ communityPoints: { type: [{ item: String, points: Number }], required: true }
},
{ timestamps: true },
)