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) {