diff --git a/sports-matcher/client/src/Layout.js b/sports-matcher/client/src/Layout.js index 908c098..3b83f9b 100644 --- a/sports-matcher/client/src/Layout.js +++ b/sports-matcher/client/src/Layout.js @@ -9,10 +9,11 @@ import NavbarToggle from "react-bootstrap/esm/NavbarToggle"; import NavbarCollapse from "react-bootstrap/esm/NavbarCollapse"; import Dashboard from "./pages/Dashboard"; import Logout from "./pages/Logout"; -import Admin from "./pages/NewAdmin"; import Rentals from "./pages/Rentals"; +import Admin from "./pages/Administration"; import Login from "./pages/Login"; import Context from "./globals.js"; +import Signup from "./pages/Signup"; export default function layout() { @@ -70,6 +71,7 @@ export default function layout() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/sports-matcher/client/src/components/AuthenticationGuard.js b/sports-matcher/client/src/components/AuthenticationGuard.js index dcb2398..130c473 100644 --- a/sports-matcher/client/src/components/AuthenticationGuard.js +++ b/sports-matcher/client/src/components/AuthenticationGuard.js @@ -13,12 +13,16 @@ export default class AuthenticationGuard extends React.Component { let userDataResponse = await apiClient.get("/user"); if (userDataResponse.status === 200) { this.context.update({ user: userDataResponse.data }); + if (this.context.user && this.context.user.accessLevel < this.props.accessLevel) { + this.context.navigate("/", { replace: true }); + } } else if (userDataResponse.status == 401) { this.context.navigate("/signup", { replace: true }); + this.context.update({ user: null }); } - if (this.context.user && this.context.user.accessLevel < this.props.accessLevel) { - this.context.navigate("/", { replace: true }); - } + } + + componentDidUpdate() { } render() { diff --git a/sports-matcher/client/src/pages/Administration.js b/sports-matcher/client/src/pages/Administration.js new file mode 100644 index 0000000..e87576d --- /dev/null +++ b/sports-matcher/client/src/pages/Administration.js @@ -0,0 +1,251 @@ +import React from "react"; +import { Button, ButtonGroup, Spinner, Table } from "react-bootstrap"; +import "../styles/Admin.css"; +import globals from "../globals"; +import AuthenticationGuard from "../components/AuthenticationGuard"; +import { apiClient } from "../utils/httpClients"; + +export default class Admin extends React.Component { + constructor(props) { + super(props); + // Use null to indicate not loaded + // Use empty array to indicate no items for that state. + this.state = { + users: null, + suspendedUsers: null, + matches: null, + user: null, + currentTab: "matches", + }; + } + + static contextType = globals; + + async componentDidMount() { + await this.loadActiveUsers(); + await this.loadSuspendedUsers(); + await this.loadMatches(); + } + + async loadActiveUsers() { + let response = await apiClient.get("/user/all/active"); + if (response.status === 200) { + this.setState({ users: response.data.active }); + } + } + + async loadSuspendedUsers() { + let response = await apiClient.get("/user/all/suspended"); + if (response.status === 200) { + this.setState({ suspendedUsers: response.data.suspended }); + } else { + console.error(response.status); + } + } + + async loadMatches() { + let response = await apiClient.get("/match/all"); + if (response.status === 200) { + this.setState({ matches: response.data.all }); + } + } + + DeleteButton() { + return ; + + } + + PardonButton() { + return ; + + } + + EditButton() { + return ; + + } + + userTableHead() { + return ( + + + ID + Username + Name + Email + Phone + + + + + ); + } + + matchTableHead() { + return ( + + + ID + Sport + Date + Location + + + + + ); + } + + + userTableData() { + if (!this.state.users) { + return ( + + + + + + + + + + ); + } + return this.state.users.map((user) => { + const { _id, firstName, lastName, email, phone } = user; + return ( + + {_id} + {firstName} + {lastName} + {email} + {phone} + {this.DeleteButton()} + {this.EditButton()} + + ); + }); + } + + susUserTableData() { + if (!this.state.suspendedUsers) { + return ( + + + + + + + + + + + ); + } + + return this.state.suspendedUsers.map((user) => { + const { _id, firstName, lastName, email, phone } = user; + return ( + + {_id} + {firstName} + {lastName} + {email} + {phone} + {this.DeleteButton()} + {this.EditButton()} + {this.PardonButton()} + + ); + }); + } + + + matchTableData() { + if (!this.state.matches) { + return ( + + + + + + + + + ); + } + + return this.state.matches.map((match) => { + const { _id, sport, when, location } = match; + const sportName = sport.name; + return ( + + {_id} + {sportName} + {when} + {location} + {this.DeleteButton()} + {this.EditButton()} + + ); + }); + } + + renderTableHead() { + if (this.state.currentTab === "matches") { + return this.matchTableHead(); + } else if (this.state.currentTab === "users") { + return this.userTableHead(); + } else { + return this.userTableHead(); + } + } + + renderTableData() { + if (this.state.currentTab === "matches") { + return this.matchTableData(); + } else if (this.state.currentTab === "users") { + return this.userTableData(); + } else { + return this.susUserTableData(); + } + } + + render() { + return ( +
+ + +
+

Administration

+ + + + + +
+ + {this.renderTableHead()} + + {this.renderTableData()} + {/* {this.matchUserTableData()} */} + +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/sports-matcher/client/src/pages/NewAdmin.js b/sports-matcher/client/src/pages/NewAdmin.js deleted file mode 100644 index d8fe446..0000000 --- a/sports-matcher/client/src/pages/NewAdmin.js +++ /dev/null @@ -1,214 +0,0 @@ -import React from "react"; -import { Button, Table } from "react-bootstrap"; -import "../styles/Admin.css"; -import globals from "../globals"; -import AuthenticationGuard from "../components/AuthenticationGuard"; - -export default class Admin extends React.Component { - constructor(props) { - super(props); - - - - this.state = { - users: [ - { id: 1, username: "username1", name: "name1", email: "user1@email.com", phone: "123-456-7890" }, - { id: 2, username: "username2", name: "name2", email: "user2@email.com", phone: "123-456-7890" }, - { id: 3, username: "username3", name: "name3", email: "user3@email.com", phone: "123-456-7890" }, - { id: 4, username: "username4", name: "name4", email: "user4@email.com", phone: "123-456-7890" } - ], - suspendedUsers: [ - { id: 1, username: "suspended1", name: "s1", email: "s1@email.com", phone: "123-456-7890" }, - { id: 2, username: "suspended2", name: "s2", email: "s2@email.com", phone: "123-456-7890" }, - { id: 3, username: "suspended3", name: "s3", email: "s3@email.com", phone: "123-456-7890" }, - { id: 4, username: "suspended4", name: "s4", email: "s4@email.com", phone: "123-456-7890" } - ], - matches: [ - { id: 1, sport: "Tennis", date: "08/08/2021", location: "toronto", description: "Tennis match" }, - { id: 2, sport: "Basketball", date: "09/09/2021", location: "toronto", description: "Basketball match" } - ], - buttonColors: ["black", "", ""], - user: null - }; - - - } - - static contextType = globals; - - async componentDidMount() { - } - - DeleteButton() { - return ; - - } - - PardonButton() { - return ; - - } - - EditButton() { - return ; - - } - - userTableHead() { - return ( - - - ID - Username - Name - Email - Phone - - - - - ); - } - - matchTableHead() { - return ( - - - ID - Sport - Date - Location - Description - - - - - ); - } - - - userTableData() { - return this.state.users.map((user) => { - const { id, username, name, email, phone } = user; - return ( - - {id} - {username} - {name} - {email} - {phone} - {this.DeleteButton()} - {this.EditButton()} - - ); - }); - } - - susUserTableData() { - return this.state.suspendedUsers.map((user) => { - const { id, username, name, email, phone } = user; - return ( - - {id} - {username} - {name} - {email} - {phone} - {this.DeleteButton()} - {this.EditButton()} - {this.PardonButton()} - - ); - }); - } - - - matchTableData() { - return this.state.matches.map((match) => { - const { id, sport, date, location, description } = match; - return ( - - {id} - {sport} - {date} - {location} - {description} - {this.DeleteButton()} - {this.EditButton()} - {this.PardonButton()} - - ); - }); - } - - selectTable() { - this.setState({ buttonColors: ["", "", ""] }); - } - - renderTableHead() { - if (this.state.buttonColors[0] === "black") { - return this.matchTableHead(); - } else if (this.state.buttonColors[1] === "black") { - return this.userTableHead(); - } else { - return this.userTableHead(); - } - } - - renderTableData() { - if (this.state.buttonColors[0] === "black") { - return this.matchTableData(); - } else if (this.state.buttonColors[1] === "black") { - return this.userTableData(); - } else { - return this.susUserTableData(); - } - - } - - - - render() { - return ( -
- - -
-

Administration

- - -
- - {this.renderTableHead()} - - {this.renderTableData()} - {/* {this.matchUserTableData()} */} - -
-
-
-
- ); - } -} \ No newline at end of file diff --git a/sports-matcher/client/src/pages/Profile.js b/sports-matcher/client/src/pages/Profile.js new file mode 100644 index 0000000..e7c6433 --- /dev/null +++ b/sports-matcher/client/src/pages/Profile.js @@ -0,0 +1,14 @@ +import React from "react"; +import { Container } from "react-bootstrap"; + +export default class Profile extends React.Component { + render() { + return ( +
+ + + +
+ ); + } +} \ No newline at end of file diff --git a/sports-matcher/client/src/pages/Signup.js b/sports-matcher/client/src/pages/Signup.js index 6ea309f..b5fa2b0 100644 --- a/sports-matcher/client/src/pages/Signup.js +++ b/sports-matcher/client/src/pages/Signup.js @@ -2,6 +2,7 @@ import React from "react"; import { Alert, Button, Card, Container, Form } from "react-bootstrap"; import { Link } from "react-router-dom"; import validator from "validator"; +import globals from "../globals"; import { apiClient } from "../utils/httpClients"; export default class Signup extends React.Component { @@ -27,9 +28,10 @@ export default class Signup extends React.Component { this.setUserState = this.setUserState.bind(this); } + static contextType = globals; async registerUser(event) { - event.preventDefault(); // We need this so that the page doesn't refresh. + event.preventDefault(); let formIssues = this.validateCurrentForm(); if (formIssues.length > 0) { this.notifyUser("Oops there were issue(s)", ( @@ -45,9 +47,11 @@ export default class Signup extends React.Component { } const res = await apiClient.post("/user", this.state.user); - console.log(res.data); if (res.status === 201) { - this.notifyUser("Success!",
You are successfully signed up! You can now go log in.
, "success"); + this.notifyUser("Success!",
You are successfully signed up! You wil be directed to login now.
, "success"); + this.redirectTimer = setTimeout(() => { + this.context.navigate("/signin", { replace: true }); + }, 1000); } else if (res.status === 409) { this.notifyUser("User exists!",
This user already exists. Try logging in instead.
, "danger"); } else if (res.status === 400) { @@ -57,8 +61,11 @@ export default class Signup extends React.Component { } } + componentWillUnmount() { + clearTimeout(this.redirectTimer); + } + validateCurrentForm() { - console.log(this.state); let formIssues = []; if (!validator.isEmail(this.state.user.email)) { formIssues.push("The email submitted is invalid."); @@ -104,8 +111,8 @@ export default class Signup extends React.Component { - Login - Welcome to Sports Matcher! + Sign up! + Welcome to Sports Matcher! Already have an account?
First name diff --git a/sports-matcher/server/controllers/matchController.js b/sports-matcher/server/controllers/matchController.js index 66f993d..6667744 100644 --- a/sports-matcher/server/controllers/matchController.js +++ b/sports-matcher/server/controllers/matchController.js @@ -1,5 +1,5 @@ import express from "express"; -import { requireAuthenticated } from "../middleware/authority.js"; +import { requireAdmin, requireAuthenticated } from "../middleware/authority.js"; import { needDatabase } from "../middleware/database.js"; import matchModel from "../schemas/matchModel.js"; import sportModel from "../schemas/sportModel.js"; @@ -46,6 +46,16 @@ MatchController.get("/recent/:limit?", needDatabase, async (req, res) => { } }); +MatchController.get("/all", requireAdmin, async (req, res) => { + try { + const allmatches = await matchModel.find().populate("sport"); + res.status(200).send({ all: allmatches }); + } catch (error) { + console.error(error); + res.status(500).send("Internal server error."); + } +}); + MatchController.get("/recent/user/:limit", needDatabase, requireAuthenticated, async (req, res) => { try { let user = req.user; diff --git a/sports-matcher/server/controllers/rentalController.js b/sports-matcher/server/controllers/rentalController.js index 616c09b..b1b55c0 100644 --- a/sports-matcher/server/controllers/rentalController.js +++ b/sports-matcher/server/controllers/rentalController.js @@ -39,7 +39,6 @@ rentalController.get("/recent/:limit?", needDatabase, async (req, res) => { let limit = parseInt(req.params.limit); if (!req.params.limit) limit = 10; if (isNaN(limit)) { - console.log(typeof (limit)); res.status(400).send("Limit parameter is not a number."); return; } diff --git a/sports-matcher/server/controllers/userController.js b/sports-matcher/server/controllers/userController.js index 3d589ca..47029b3 100644 --- a/sports-matcher/server/controllers/userController.js +++ b/sports-matcher/server/controllers/userController.js @@ -1,5 +1,4 @@ import express from "express"; -import validator from "validator"; import { requireAdmin, requireAuthenticated } from "../middleware/authority.js"; import { needDatabase } from "../middleware/database.js"; import userModel from "../schemas/userModel.js"; @@ -125,14 +124,20 @@ UserController.patch("/:id?", needDatabase, requireAuthenticated, async (req, re } }); +UserController.get("/all", requireAdmin, async (req, res) => { + try { + let all = await userModel.find(); + res.status(200).send({ all: all }); + } catch (error) { + console.error(error); + res.status(500).send("Internal server error"); + } +}); + UserController.get("/all/active", requireAdmin, async (req, res) => { try { - if (req.user.accessLevel < 3) { - res.status(401).send("You do not have the required privileges."); - return; - } - let res = await userModel.find().where("suspend").lt(Date.now); - res.status(200).send({ all: res }); + let active = await userModel.find().where("suspend").lt(Date.now()); + res.status(200).send({ active: active }); } catch (error) { console.error(error); res.status(500).send("Internal server error"); @@ -141,8 +146,8 @@ UserController.get("/all/active", requireAdmin, async (req, res) => { UserController.get("/all/suspended", requireAuthenticated, async (req, res) => { try { - let res = await userModel.find().where("suspend").gte(Date.now); - res.status(200).send({ suspended: res }); + let suspended = await userModel.find().where("suspend").gte(Date.now()); + res.status(200).send({ suspended: suspended }); } catch (error) { console.error(error); res.status(500).send("Internal server error"); diff --git a/sports-matcher/server/schemas/matchModel.js b/sports-matcher/server/schemas/matchModel.js index 85c55ee..0e0dae5 100644 --- a/sports-matcher/server/schemas/matchModel.js +++ b/sports-matcher/server/schemas/matchModel.js @@ -21,7 +21,7 @@ const matchSchema = new mongoose.Schema({ participants: { type: [{ type: Types.ObjectId, ref: ModelNameRegister.User }], required: true, default: [] }, difficulty: { type: Number, required: true }, sport: { type: Types.ObjectId, ref: ModelNameRegister.Sport }, - createDate: { type: Date, required: true, default: Date.now } + createDate: { type: Date, required: true, default: Date.now() } }); matchSchema.pre("remove", function (next) { diff --git a/sports-matcher/server/schemas/rentalModel.js b/sports-matcher/server/schemas/rentalModel.js index 31e2d24..409d059 100644 --- a/sports-matcher/server/schemas/rentalModel.js +++ b/sports-matcher/server/schemas/rentalModel.js @@ -8,7 +8,7 @@ const rentalSchema = new mongoose.Schema({ rate: { type: String, required: true, trim: true }, description: { type: String, required: true }, contact: { type: String, required: true }, - createDate: { type: Date, required: true, default: Date.now }, + createDate: { type: Date, required: true, default: Date.now() }, creator: { type: Types.ObjectId, ref: modelNameRegister.User } }); diff --git a/sports-matcher/server/schemas/userModel.js b/sports-matcher/server/schemas/userModel.js index 1a98925..0bf9195 100644 --- a/sports-matcher/server/schemas/userModel.js +++ b/sports-matcher/server/schemas/userModel.js @@ -19,7 +19,7 @@ const userSchema = new mongoose.Schema({ }, firstName: { type: String, required: true, trim: true }, lastName: { type: String, required: true, trim: true }, - joinDate: { type: Date, default: Date.now, required: true }, + joinDate: { type: Date, default: Date.now(), required: true }, phone: { type: Number, required: false, min: 0 }, password: { type: String, @@ -36,7 +36,7 @@ const userSchema = new mongoose.Schema({ participatingMatchesPublicity: { type: Boolean, required: true, default: false }, friends: { type: Types.ObjectId, ref: modelNameRegister.User }, accessLevel: { type: Number, required: true, default: 0 }, - suspend: { type: Date, required: true, default: Date.now } // suspend the user until the when the user was created. + suspend: { type: Date, required: true, default: Date.now() } // suspend the user until the when the user was created. }); userSchema.statics.credentialsExist = async function (email, password) {