diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 000000000..8d1a03a33
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,64 @@
+name: 'Bug Report'
+description: 'Bug report HackforLA issue form.'
+
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Describe the Bug
+ description: Clearly state the bug
+ validations:
+ required: true
+ - type: dropdown
+ id: feature
+ attributes:
+ label: Feature
+ description: Select a feature related to the bug
+ options:
+ - AWS
+ - Brand
+ - Login
+ - Users
+ - Agenda
+ - Check in
+ - Database
+ - Dashboard
+ - Marketing
+ - Onboarding
+ - Repo Update
+ - Documentation
+ - GitHub Actions
+ - GitHub Hygiene
+ - Infrastructure
+ - Package Update
+ - ESLint Warnings
+ - Form validation
+ - Recurring Events
+ - Project Management
+ - Account Setup Automation
+ - Meeting Time Change Ticket
+ - Add/Remove PM access
+ validations:
+ required: true
+ - type: textarea
+ id: replicate
+ attributes:
+ label: How to Replicate
+ description: Clearly state how to replicate the bug
+ validations:
+ required: true
+ - type: dropdown
+ id: notification
+ attributes:
+ label: Request notification after bug squashing
+ description: Request notification after a bug is squashed
+ options:
+ - Yes
+ - No
+ validations:
+ required: true
+ - type: textarea
+ id: resource
+ attributes:
+ label: Resources/Information
+ description: Include any important resources/information
\ No newline at end of file
diff --git a/.github/workflows/New-issue-create-card.yml b/.github/workflows/New-issue-create-card.yml
index 1ab098dbd..d64361822 100644
--- a/.github/workflows/New-issue-create-card.yml
+++ b/.github/workflows/New-issue-create-card.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Create or Update Project Card
- uses: peter-evans/create-or-update-project-card@v1
+ uses: peter-evans/create-or-update-project-card@v2
with:
- project-name: VRMS v0.4
+ project-name: VRMS - Active Project Board
column-name: New Issue Approval
diff --git a/.github/workflows/aws-backend-deploy.yml b/.github/workflows/aws-backend-deploy.yml
index 6b97b6173..277ad8e5b 100644
--- a/.github/workflows/aws-backend-deploy.yml
+++ b/.github/workflows/aws-backend-deploy.yml
@@ -58,7 +58,7 @@ jobs:
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Init Docker Cache
- uses: satackey/action-docker-layer-caching@v0.0.11
+ uses: jpribyl/action-docker-layer-caching@v0.1.0
with:
key: ${{ github.workflow }}-2-{hash}
restore-keys: |
diff --git a/.github/workflows/aws-frontend-deploy.yml b/.github/workflows/aws-frontend-deploy.yml
index 313c7694c..9b2b2bbe1 100644
--- a/.github/workflows/aws-frontend-deploy.yml
+++ b/.github/workflows/aws-frontend-deploy.yml
@@ -58,7 +58,7 @@ jobs:
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Init Docker Cache
- uses: satackey/action-docker-layer-caching@v0.0.11
+ uses: jpribyl/action-docker-layer-caching@v0.1.0
with:
key: ${{ github.workflow }}-2-{hash}
restore-keys: |
diff --git a/.github/workflows/waiting-to-merge.yaml b/.github/workflows/waiting-to-merge.yaml
new file mode 100644
index 000000000..9b41dc9ac
--- /dev/null
+++ b/.github/workflows/waiting-to-merge.yaml
@@ -0,0 +1,17 @@
+name: Waiting to Merge
+
+on:
+ pull_request:
+ types: [synchronize, opened, reopened, labeled, unlabeled]
+
+jobs:
+ do-not-merge:
+ if: ${{ contains(github.event.*.labels.*.name, 'waiting to merge') }}
+ name: Prevent Merging
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check for label
+ run: |
+ echo "Pull request is labeled as 'waiting to merge'"
+ echo "This workflow fails so that the pull request cannot be merged"
+ exit 1
diff --git a/backend/middleware/user.middleware.js b/backend/middleware/user.middleware.js
index 6e675313c..4d419ab90 100644
--- a/backend/middleware/user.middleware.js
+++ b/backend/middleware/user.middleware.js
@@ -15,7 +15,7 @@ function isAdminByEmail(req, res, next) {
return res.sendStatus(400);
} else {
const role = user.accessLevel;
- if (req.get('origin').includes('3001') || role === 'admin' || user.managedProjects.length > 0) {
+ if (role === 'admin' || user.managedProjects.length > 0) {
next();
} else {
next(res.sendStatus(401));
diff --git a/backend/models/project.model.js b/backend/models/project.model.js
index e345b2c4f..47421aa47 100644
--- a/backend/models/project.model.js
+++ b/backend/models/project.model.js
@@ -16,19 +16,19 @@ Idea for the future: programmingLanguages, numberGithubContributions (pull these
*/
const projectSchema = mongoose.Schema({
- name: { type: String },
- description: { type: String },
- githubIdentifier: { type: String },
+ name: { type: String, trim: true },
+ description: { type: String, trim: true },
+ githubIdentifier: { type: String, trim: true },
projectStatus: { type: String }, // Active, Completed, or Paused
- location: { type: String }, // DTLA, Westside, South LA, or Remote (hacknight)
+ location: { type: String, trim: true }, // DTLA, Westside, South LA, or Remote (hacknight)
//teamMembers: { type: String }, // commented since we should be able to get this from Project Team Members table
createdDate: { type: Date, default: Date.now }, // date/time project was created
completedDate: { type: Date }, // only if Status = Completed, date/time completed
- githubUrl: { type: String }, // link to main repo
- slackUrl: { type: String }, // link to Slack channel
- googleDriveUrl: { type: String },
+ githubUrl: { type: String, trim: true }, // link to main repo
+ slackUrl: { type: String, trim: true }, // link to Slack channel
+ googleDriveUrl: { type: String, trim: true },
googleDriveId: { type: String },
- hflaWebsiteUrl: { type: String },
+ hflaWebsiteUrl: { type: String, trim: true },
videoConferenceLink: { type: String },
lookingDescription: { type: String }, // narrative on what the project is looking for
recruitingCategories: [{ type: String }], // same as global Skills picklist
diff --git a/backend/models/user.model.js b/backend/models/user.model.js
index 0534d6107..1424a3753 100644
--- a/backend/models/user.model.js
+++ b/backend/models/user.model.js
@@ -33,9 +33,10 @@ const userSchema = mongoose.Schema({
isHflaGithubMember: { type: Boolean }, // pull from API once github handle in place?
githubPublic2FA: { type: Boolean }, // does the user have 2FA enabled on their github and membership set to public?
availability: { type: String }, // availability to meet outside of hacknight times; string for now, more structured in future
- managedProjects: [{ type: String}] // Which projects managed by user.
+ managedProjects: [{ type: String}], // Which projects managed by user.
//currentProject: { type: String } // no longer need this as we can get it from Project Team Member table
// password: { type: String, required: true }
+ isActive: { type: Boolean, default: true }
});
userSchema.methods.serialize = function () {
@@ -65,7 +66,8 @@ userSchema.methods.serialize = function () {
isHflaGithubMember: this.isHflaGithubMember,
githubPublic2FA: this.githubPublic2FA,
availability: this.availability,
- managedProjects: this.managedProjects
+ managedProjects: this.managedProjects,
+ isActive: this.isActive
};
};
diff --git a/backend/package.json b/backend/package.json
index ff91edf63..5fe900b1b 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -21,7 +21,7 @@
"@babel/eslint-plugin": "^7.14.5",
"@shelf/jest-mongodb": "^1.2.3",
"concurrently": "^5.1.0",
- "debug": "^4.1.1",
+ "debug": "^4.3.1",
"eslint": "^7.9.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-airbnb-base": "^14.2.0",
diff --git a/backend/routers/projects.router.js b/backend/routers/projects.router.js
index 8991000cb..cfa217b07 100644
--- a/backend/routers/projects.router.js
+++ b/backend/routers/projects.router.js
@@ -2,15 +2,18 @@ const express = require("express");
const router = express.Router();
const { ProjectController } = require('../controllers');
+const { AuthUtil } = require("../middleware");
// The base is /api/projects
router.get('/', ProjectController.project_list);
-router.post('/', ProjectController.create);
+router.post('/', AuthUtil.verifyCookie, ProjectController.create);
router.get('/:ProjectId', ProjectController.project_by_id);
-router.patch('/:ProjectId', ProjectController.update);
+router.put('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update);
+
+router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update);
module.exports = router;
diff --git a/backend/routers/projects.router.test.js b/backend/routers/projects.router.test.js
index 5233247b9..e7ebf7129 100644
--- a/backend/routers/projects.router.test.js
+++ b/backend/routers/projects.router.test.js
@@ -1,18 +1,58 @@
const supertest = require('supertest');
const app = require('../app');
const request = supertest(app);
+const jwt = require('jsonwebtoken');
+const { CONFIG_AUTH } = require('../config');
+
const { setupDB } = require('../setup-test');
setupDB('api-projects');
-const { Project } = require('../models');
+const { Project, User } = require('../models');
const CONFIG = require('../config/auth.config');
const headers = {};
headers['x-customrequired-header'] = CONFIG.CUSTOM_REQUEST_HEADER;
headers.Accept = 'application/json';
+headers.authorization = 'Bearer sometoken';
+
+let token;
+
describe('CREATE', () => {
+ beforeAll( async () => {
+ const submittedData = {
+ name: {
+ firstName: 'test',
+ lastName: 'user',
+ },
+ email: 'newtest@test.com',
+ };
+ const user = await User.create(submittedData);
+ const auth_origin = 'TEST';
+ token = jwt.sign(
+ { id: user.id, role: user.accessLevel, auth_origin },
+ CONFIG_AUTH.SECRET,
+ {
+ expiresIn: `${CONFIG_AUTH.TOKEN_EXPIRATION_SEC}s`,
+ },
+ );
+ })
+ test('Create a Project with POST to /api/projects/ without token', async (done) => {
+ // Test Data
+ const submittedData = {
+ name: 'projectName',
+ };
+
+ // Submit a project
+ const res = await request
+ .post('/api/projects/')
+ .set(headers)
+ .send(submittedData);
+ expect(res.status).toBe(401);
+ done();
+ });
+
test('Create a Project with POST to /api/projects/', async (done) => {
// Test Data
const submittedData = {
@@ -23,6 +63,7 @@ describe('CREATE', () => {
const res = await request
.post('/api/projects/')
.set(headers)
+ .set('Cookie', [`token=${token}`] )
.send(submittedData);
expect(res.status).toBe(201);
done();
@@ -40,6 +81,7 @@ describe('READ', () => {
const res = await request
.post('/api/projects/')
.set(headers)
+ .set('Cookie', [`token=${token}`])
.send(submittedData);
expect(res.status).toBe(201);
@@ -54,7 +96,25 @@ describe('READ', () => {
});
describe('UPDATE', () => {
- test('Update a project with PATCH to /api/projects/:id', async (done) => {
+ beforeAll(async () => {
+ const submittedData = {
+ name: {
+ firstName: 'test',
+ lastName: 'user',
+ },
+ email: 'newtest@test.com',
+ };
+ const user = await User.create(submittedData);
+ const auth_origin = 'TEST';
+ token = jwt.sign(
+ { id: user.id, role: user.accessLevel, auth_origin },
+ CONFIG_AUTH.SECRET,
+ {
+ expiresIn: `${CONFIG_AUTH.TOKEN_EXPIRATION_SEC}s`,
+ },
+ );
+ })
+ test('Update a project with PATCH to /api/projects/:id without a token', async (done) => {
// Test Data
const submittedData = {
name: 'projectName',
@@ -64,6 +124,7 @@ describe('UPDATE', () => {
const res = await request
.post('/api/projects/')
.set(headers)
+ .set('Cookie', [`token=${token}`])
.send(submittedData);
expect(res.status).toBe(201);
@@ -73,13 +134,47 @@ describe('UPDATE', () => {
// Update project
const res2 = await request
- .patch(`/api/projects/${res.body._id}`)
+ .put(`/api/projects/${res.body._id}`)
.set(headers)
.send(updatedDataPayload);
- expect(res2.status).toBe(200);
+ expect(res2.status).toBe(401);
// Get project
- const res3 = await request.get(`/api/projects/${res.body._id}`).set(headers);
+ const res3 = await request.get(`/api/projects/${res.body._id}`)
+ .set(headers);
+ expect(res3.status).toBe(200);
+ done();
+ });
+ test('Update a project with PATCH to /api/projects/:id with a token', async (done) => {
+ // Test Data
+ const submittedData = {
+ name: 'projectName',
+ };
+
+ // Submit a project
+ const res = await request
+ .post('/api/projects/')
+ .set(headers)
+ .set('Cookie', [`token=${token}`])
+ .send(submittedData);
+ expect(res.status).toBe(201);
+
+ const updatedDataPayload = {
+ name: 'updatedProjectName',
+ };
+
+ // Update project
+ const res2 = await request
+ .put(`/api/projects/${res.body._id}`)
+ .set(headers)
+ .set('Cookie', [`token=${token}`])
+ .send(updatedDataPayload);
+ expect(res2.status).toBe(200)
+
+ // Get project
+ const res3 = await request.get(`/api/projects/${res.body._id}`)
+ .set(headers)
+ .set('Cookie', [`token=${token}`])
expect(res3.status).toBe(200);
const APIData = res3.body;
@@ -89,7 +184,45 @@ describe('UPDATE', () => {
});
describe('DELETE', () => {
- test('Delete a project with POST to /api/projects/:id', async (done) => {
+ beforeAll(async () => {
+ const submittedData = {
+ name: {
+ firstName: 'test',
+ lastName: 'user',
+ },
+ email: 'newtest@test.com',
+ };
+ const user = await User.create(submittedData);
+ const auth_origin = 'TEST';
+ token = jwt.sign(
+ { id: user.id, role: user.accessLevel, auth_origin },
+ CONFIG_AUTH.SECRET,
+ {
+ expiresIn: `${CONFIG_AUTH.TOKEN_EXPIRATION_SEC}s`,
+ },
+ );
+ })
+ test('Delete a project with POST to /api/projects/:id without a token', async (done) => {
+ // Test Data
+ const submittedData = {
+ name: 'projectName',
+ };
+
+ // Submit a project
+ const res = await request
+ .post('/api/projects/')
+ .set(headers)
+ .set('Cookie', [`token=${token}`])
+ .send(submittedData);
+ expect(res.status).toBe(201);
+
+ // Delete project
+ const res2 = await request.patch(`/api/projects/${res.body._id}`)
+ .set(headers);
+ expect(res2.status).toBe(401);
+ done();
+});
+ test('Delete a project with POST to /api/projects/:id with a token', async (done) => {
// Test Data
const submittedData = {
name: 'projectName',
@@ -99,11 +232,14 @@ describe('DELETE', () => {
const res = await request
.post('/api/projects/')
.set(headers)
+ .set('Cookie', [`token=${token}`])
.send(submittedData);
expect(res.status).toBe(201);
// Delete project
- const res2 = await request.patch(`/api/projects/${res.body._id}`).set(headers);
+ const res2 = await request.patch(`/api/projects/${res.body._id}`)
+ .set(headers)
+ .set('Cookie', [`token=${token}`])
expect(res2.status).toBe(200);
done();
});
diff --git a/backend/routers/recurringEvents.router.js b/backend/routers/recurringEvents.router.js
index 66e5ab7db..23481745d 100644
--- a/backend/routers/recurringEvents.router.js
+++ b/backend/routers/recurringEvents.router.js
@@ -4,6 +4,7 @@ const cors = require('cors');
const { RecurringEvent } = require('../models/recurringEvent.model');
const { RecurringEventController } = require('../controllers/');
+const { AuthUtil } = require('../middleware');
// GET /api/recurringevents/
router.get('/', cors(), (req, res) => {
@@ -34,10 +35,10 @@ router.get('/:id', (req, res) => {
});
});
-router.post('/', RecurringEventController.create);
+router.post('/', AuthUtil.verifyCookie, RecurringEventController.create);
-router.patch('/:RecurringEventId', RecurringEventController.update);
+router.patch('/:RecurringEventId', AuthUtil.verifyCookie, RecurringEventController.update);
-router.delete('/:RecurringEventId', RecurringEventController.destroy);
+router.delete('/:RecurringEventId', AuthUtil.verifyCookie, RecurringEventController.destroy);
module.exports = router;
diff --git a/backend/workers/closeCheckins.js b/backend/workers/closeCheckins.js
index a9dc0a0e8..9d04cd7da 100644
--- a/backend/workers/closeCheckins.js
+++ b/backend/workers/closeCheckins.js
@@ -3,7 +3,7 @@ module.exports = (cron, fetch) => {
// Check to see if any events are about to start,
// and if so, open their respective check-ins
- const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : 'http://localhost:4000';
+ const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`;
const headerToSend = process.env.CUSTOM_REQUEST_HEADER;
async function fetchEvents() {
@@ -28,6 +28,12 @@ module.exports = (cron, fetch) => {
if (events && events.length > 0) {
const sortedEvents = events.filter(event => {
+ if (!event.date) {
+ // handle if event date is null/undefined
+ // false meaning don't include in sortedEvents
+ return false
+ }
+
const currentTimeISO = new Date().toISOString();
const threeHoursFromStartTime = new Date(event.date).getTime() + 10800000;
const threeHoursISO = new Date(threeHoursFromStartTime).toISOString();
diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js
index 40bcafc0e..3eef5fbf5 100644
--- a/backend/workers/createRecurringEvents.js
+++ b/backend/workers/createRecurringEvents.js
@@ -8,7 +8,7 @@ module.exports = (cron, fetch) => {
let RECURRING_EVENTS;
let TODAY_DATE;
let TODAY;
- const URL = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : 'http://localhost:4000';
+ const URL = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`;
const headerToSend = process.env.CUSTOM_REQUEST_HEADER;
const fetchEvents = async () => {
@@ -60,52 +60,58 @@ module.exports = (cron, fetch) => {
// forEach function with async/await.
for (filteredEvent of filteredEvents) {
const eventExists = await checkIfEventExists(filteredEvent.name);
- // console.log('Event exists? ', eventExists);
- const eventDate = new Date(filteredEvent.date);
if (eventExists) {
- console.log("Not going to run ceateEvent");
+ //Do nothing
+ console.log("➖ Not going to run ceateEvent");
} else {
// Create new event
- const hours = eventDate.getHours();
- const minutes = eventDate.getMinutes();
- const seconds = eventDate.getSeconds();
- const milliseconds = eventDate.getMilliseconds();
-
- const yearToday = TODAY_DATE.getFullYear();
- const monthToday = TODAY_DATE.getMonth();
- const dateToday = TODAY_DATE.getDate();
-
- const newEventDate = new Date(yearToday, monthToday, dateToday, hours, minutes, seconds, milliseconds);
-
- const newEndTime = new Date(yearToday, monthToday, dateToday, hours + filteredEvent.hours, minutes, seconds, milliseconds)
-
- const eventToCreate = {
- name: filteredEvent.name && filteredEvent.name,
- hacknight: filteredEvent.hacknight && filteredEvent.hacknight,
- eventType: filteredEvent.eventType && filteredEvent.eventType,
- description: filteredEvent.eventDescription && filteredEvent.eventDescription,
- project: filteredEvent.project && filteredEvent.project,
- date: filteredEvent.date && newEventDate,
- startTime: filteredEvent.startTime && newEventDate,
- endTime: filteredEvent.endTime && newEndTime,
- hours: filteredEvent.hours && filteredEvent.hours
- }
- if (filteredEvent.hasOwnProperty("location")) {
- eventToCreate.location = {
- city: filteredEvent.location.city ? filteredEvent.location.city : 'REMOTE',
- state: filteredEvent.location.state ? filteredEvent.location.state : 'REMOTE',
- country: filteredEvent.location.country ? filteredEvent.location.country : 'REMOTE'
- };
- }
-
+ const eventToCreate = generateEventData(filteredEvent);
+
const created = await createEvent(eventToCreate);
- console.log(created);
+ console.log("➕", created);
};
};
};
};
+ function generateEventData(eventData, TODAY_DATE = new Date()) {
+ const eventDate = new Date(eventData.startTime);
+ // Create new event
+ const hours = eventDate.getHours();
+ const minutes = eventDate.getMinutes();
+ const seconds = eventDate.getSeconds();
+ const milliseconds = eventDate.getMilliseconds();
+
+ const yearToday = TODAY_DATE.getFullYear();
+ const monthToday = TODAY_DATE.getMonth();
+ const dateToday = TODAY_DATE.getDate();
+
+ const newEventDate = new Date(yearToday, monthToday, dateToday, hours, minutes, seconds, milliseconds);
+
+ const newEndTime = new Date(yearToday, monthToday, dateToday, hours + filteredEvent.hours, minutes, seconds, milliseconds)
+
+ const eventToCreate = {
+ name: eventData.name && eventData.name,
+ hacknight: eventData.hacknight && eventData.hacknight,
+ eventType: eventData.eventType && eventData.eventType,
+ description: eventData.eventDescription && eventData.eventDescription,
+ project: eventData.project && eventData.project,
+ date: eventData.date && newEventDate,
+ startTime: eventData.startTime && newEventDate,
+ endTime: eventData.endTime && newEndTime,
+ hours: eventData.hours && eventData.hours
+ }
+ if (eventData.hasOwnProperty("location")) {
+ eventToCreate.location = {
+ city: eventData.location.city ? eventData.location.city : 'REMOTE',
+ state: eventData.location.state ? eventData.location.state : 'REMOTE',
+ country: eventData.location.country ? eventData.location.country : 'REMOTE'
+ };
+ }
+ return eventToCreate
+ };
+
async function checkIfEventExists(eventName) {
const events = EVENTS;
// const today = new Date();
diff --git a/backend/workers/openCheckins.js b/backend/workers/openCheckins.js
index bd1191533..18d917d92 100644
--- a/backend/workers/openCheckins.js
+++ b/backend/workers/openCheckins.js
@@ -3,7 +3,7 @@ module.exports = (cron, fetch) => {
// Check to see if any events are about to start,
// and if so, open their respective check-ins
- const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : 'http://localhost:4000';
+ const url = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`;
const headerToSend = process.env.CUSTOM_REQUEST_HEADER;
async function fetchEvents() {
diff --git a/backend/workers/slackbot.js b/backend/workers/slackbot.js
index 27748a78d..d50e56f9b 100644
--- a/backend/workers/slackbot.js
+++ b/backend/workers/slackbot.js
@@ -9,7 +9,7 @@ module.exports = (fetch) => {
const fetchEvents = async () => {
try {
- const res = await fetch('http://localhost:4000/api/events', {
+ const res = await fetch(`http://localhost:${process.env.BACKEND_PORT}/api/events`, {
headers: {
'x-customrequired-header': headerToSend,
},
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 2a1c3c069..938ab974a 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -16,6 +16,14 @@
dependencies:
"@babel/highlight" "^7.14.5"
+"@babel/code-frame@^7.22.13":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
+ dependencies:
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
+
"@babel/compat-data@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176"
@@ -80,7 +88,7 @@
dependencies:
eslint-rule-composer "^0.3.0"
-"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
+"@babel/generator@^7.11.6":
version "7.11.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620"
integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==
@@ -98,6 +106,16 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+ integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+ dependencies:
+ "@babel/types" "^7.23.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-compilation-targets@^7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818"
@@ -108,44 +126,25 @@
browserslist "^4.16.6"
semver "^6.3.0"
-"@babel/helper-function-name@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
- integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
- dependencies:
- "@babel/helper-get-function-arity" "^7.10.4"
- "@babel/template" "^7.10.4"
- "@babel/types" "^7.10.4"
-
-"@babel/helper-function-name@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4"
- integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==
- dependencies:
- "@babel/helper-get-function-arity" "^7.14.5"
- "@babel/template" "^7.14.5"
- "@babel/types" "^7.14.5"
-
-"@babel/helper-get-function-arity@^7.10.4":
- version "7.10.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
- integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
- dependencies:
- "@babel/types" "^7.10.4"
+"@babel/helper-environment-visitor@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
-"@babel/helper-get-function-arity@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815"
- integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==
+"@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
- "@babel/types" "^7.14.5"
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
-"@babel/helper-hoist-variables@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d"
- integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
- "@babel/types" "^7.14.5"
+ "@babel/types" "^7.22.5"
"@babel/helper-member-expression-to-functions@^7.10.4":
version "7.11.0"
@@ -270,6 +269,18 @@
dependencies:
"@babel/types" "^7.14.5"
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
"@babel/helper-validator-identifier@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
@@ -280,6 +291,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
"@babel/helper-validator-option@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
@@ -321,6 +337,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5":
version "7.11.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
@@ -331,6 +356,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862"
integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==
+"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+ integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
+
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
@@ -441,33 +471,28 @@
"@babel/parser" "^7.14.5"
"@babel/types" "^7.14.5"
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5":
- version "7.11.5"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3"
- integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==
- dependencies:
- "@babel/code-frame" "^7.10.4"
- "@babel/generator" "^7.11.5"
- "@babel/helper-function-name" "^7.10.4"
- "@babel/helper-split-export-declaration" "^7.11.0"
- "@babel/parser" "^7.11.5"
- "@babel/types" "^7.11.5"
- debug "^4.1.0"
- globals "^11.1.0"
- lodash "^4.17.19"
-
-"@babel/traverse@^7.15.0":
- version "7.15.0"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98"
- integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==
- dependencies:
- "@babel/code-frame" "^7.14.5"
- "@babel/generator" "^7.15.0"
- "@babel/helper-function-name" "^7.14.5"
- "@babel/helper-hoist-variables" "^7.14.5"
- "@babel/helper-split-export-declaration" "^7.14.5"
- "@babel/parser" "^7.15.0"
- "@babel/types" "^7.15.0"
+"@babel/template@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.15.0":
+ version "7.23.2"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
+ integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.23.0"
+ "@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@@ -488,6 +513,15 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
+"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+ integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -704,6 +738,38 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+"@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
+ integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@shelf/jest-mongodb@^1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@shelf/jest-mongodb/-/jest-mongodb-1.2.3.tgz#7cb34f0bcb71871b0d1c8d16a4f1fdb18b1620df"
@@ -2005,10 +2071,10 @@ debug@3.1.0, debug@=3.1.0:
dependencies:
ms "2.0.0"
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
- integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
+debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+ integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
@@ -6816,9 +6882,9 @@ widest-line@^3.1.0:
string-width "^4.0.0"
word-wrap@^1.2.3, word-wrap@~1.2.3:
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
- integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
wrap-ansi@^5.1.0:
version "5.1.0"
diff --git a/client/src/App.js b/client/src/App.js
index 7a2d51b7f..82fdf5baa 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -21,12 +21,11 @@ import ProjectLeaderDashboard from './pages/ProjectLeaderDashboard';
import UserAdmin from './pages/UserAdmin';
import ProjectList from './pages/ProjectList';
import ManageProjects from './pages/ManageProjects';
+import addProject from './components/manageProjects/addProject';
import HealthCheck from './pages/HealthCheck';
import SecretPassword from './pages/SecretPassword';
import UserWelcome from './pages/UserWelcome';
-import ProjectForm from './components/ProjectForm';
-
import { ThemeProvider } from '@mui/material';
import theme from './theme';
@@ -49,7 +48,7 @@ const routes = [
{ path: '/events', name: 'events', Component: Events },
{ path: '/useradmin', name: 'useradmin', Component: UserAdmin },
{ path: '/projects', name: 'projects', Component: ProjectList },
- { path: '/projects/create', name: 'projectform', Component: ProjectForm },
+ { path: '/projects/create', name: 'projectform', Component: addProject},
{
path: '/projects/:projectId',
name: 'project',
diff --git a/client/src/api/EventsApiService.js b/client/src/api/EventsApiService.js
new file mode 100644
index 000000000..d858f2876
--- /dev/null
+++ b/client/src/api/EventsApiService.js
@@ -0,0 +1,78 @@
+import { REACT_APP_CUSTOM_REQUEST_HEADER } from '../utils/globalSettings';
+
+class EventsApiService {
+ constructor() {
+ this.headers = {
+ 'Content-Type': 'application/json',
+ 'x-customrequired-header': REACT_APP_CUSTOM_REQUEST_HEADER,
+ };
+ this.baseUrl = '/api/events/';
+ }
+
+ async fetchEvents() {
+ try {
+ const threeWeeksInMilliseconds = 3 * 7 * 24 * 60 * 60 * 1000; // 3 weeks in milliseconds
+ const threeWeeksAgo = new Date();
+ threeWeeksAgo.setTime(threeWeeksAgo.getTime() - threeWeeksInMilliseconds);
+
+ const dateQuery = `?date[$gt]=${threeWeeksAgo.toISOString().split('.')[0]+"Z"}`;
+
+ const res = await fetch(this.baseUrl + dateQuery, {
+ headers: this.headers,
+ });
+ return await res.json();
+ } catch (error) {
+ console.log(`fetchEvents error: ${error}`);
+ alert('Server not responding. Please refresh the page.');
+ return [];
+ }
+ }
+
+ async createNewEvent(eventToCreate) {
+ const requestOptions = {
+ method: 'POST',
+ headers: this.headers,
+ body: JSON.stringify(eventToCreate),
+ };
+
+ try {
+ return await fetch(this.baseUrl, requestOptions);
+ } catch (error) {
+ console.error(`Add event error: `, error);
+ alert('Server not responding. Please try again.');
+ return undefined;
+ }
+ }
+
+ async deleteEvent(recurringEventID) {
+ const requestOptions = {
+ method: 'DELETE',
+ headers: this.headers,
+ };
+
+ try {
+ return await fetch(`${this.baseUrl}/${recurringEventID}`, requestOptions);
+ } catch (error) {
+ console.error(`Delete event error: `, error);
+ alert('Server not responding. Please try again.');
+ return undefined;
+ }
+ }
+
+ async updateEvent(eventToUpdate, eventID) {
+ const requestOptions = {
+ method: 'PATCH',
+ headers: this.headers,
+ body: JSON.stringify(eventToUpdate),
+ };
+ try {
+ return await fetch(`${this.baseUrl}${eventID}`, requestOptions);
+ } catch (error) {
+ console.error(`Update event error: `, error);
+ alert('Server not responding. Please try again.');
+ return undefined;
+ }
+ }
+}
+
+export default EventsApiService;
diff --git a/client/src/api/ProjectApiService.js b/client/src/api/ProjectApiService.js
index b97e88cf2..f25711c0e 100644
--- a/client/src/api/ProjectApiService.js
+++ b/client/src/api/ProjectApiService.js
@@ -22,29 +22,30 @@ class ProjectApiService {
}
}
+ // Handles the POST request and returns the projects ID.
async create(projectData) {
const {
name,
description,
location,
- githubIdentifier,
githubUrl,
slackUrl,
googleDriveUrl,
hflaWebsiteUrl,
+ githubIdentifier,
} = projectData;
const requestOptions = {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
- name: name,
- description: description,
- location: location,
- githubIdentifier: githubIdentifier,
- githubUrl: githubUrl,
- slackUrl: slackUrl,
- googleDriveUrl: googleDriveUrl,
- hflaWebsiteUrl: hflaWebsiteUrl,
+ name,
+ description,
+ location,
+ githubUrl,
+ slackUrl,
+ googleDriveUrl,
+ hflaWebsiteUrl,
+ githubIdentifier,
projectStatus: 'Active',
}),
};
@@ -62,26 +63,13 @@ class ProjectApiService {
}
}
- async updateProject(projectId, fieldName, fieldValue) {
- let updateValue = fieldValue;
- // These field are arrays, but the form makes them comma separated strings,
- // so this adds it back to db as an arrray.
- if (
- fieldValue &&
- (fieldName === 'partners' || fieldName === 'recruitingCategories')
- ) {
- updateValue = fieldValue
- .split(',')
- .filter((x) => x !== '')
- .map((y) => y.trim());
- }
-
+ async updateProject(projectId, projectData) {
// Update database
const url = `${this.baseProjectUrl}${projectId}`;
const requestOptions = {
- method: 'PATCH',
+ method: 'PUT',
headers: this.headers,
- body: JSON.stringify({ [fieldName]: updateValue }),
+ body: JSON.stringify({ ...projectData }),
};
try {
diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js
index 0e7173edd..d0c5506de 100644
--- a/client/src/api/UserApiService.js
+++ b/client/src/api/UserApiService.js
@@ -40,6 +40,39 @@ class UserApiService {
}
return undefined;
}
+
+ async updateUserDbIsActive(userToEdit, isActive) {
+ const url = `${this.baseUserUrl}${userToEdit._id}`;
+ const requestOptions = {
+ method: 'PATCH',
+ headers: this.headers,
+ body: JSON.stringify({ isActive }),
+ };
+
+ try {
+ return await fetch(url, requestOptions);
+ } catch (err) {
+ console.error('update is-active error', err);
+ alert('server not responding. Please try again.');
+ }
+ }
+
+ async updateUserDbEmail(userToEdit, email) {
+ const url = `${this.baseUserUrl}${userToEdit._id}`;
+ const lowercaseEmail = email.toLowerCase();
+ const requestOptions = {
+ method: 'PATCH',
+ headers: this.headers,
+ body: JSON.stringify({ lowercaseEmail }),
+ };
+
+ try {
+ return await fetch(url, requestOptions);
+ } catch (err) {
+ console.error('update use email error', err);
+ alert('server not responding. Please try again.');
+ }
+ }
}
export default UserApiService;
diff --git a/client/src/components/ProjectForm.js b/client/src/components/ProjectForm.js
index e9cd58532..309f1c109 100644
--- a/client/src/components/ProjectForm.js
+++ b/client/src/components/ProjectForm.js
@@ -1,14 +1,10 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
-import ProjectApiService from '../api/ProjectApiService';
-import { ReactComponent as PlusIcon } from '../svg/PlusIcon.svg';
-
+import { useForm } from 'react-hook-form';
+import { Redirect } from 'react-router-dom';
import {
Typography,
Box,
- Divider,
- TextField,
- InputLabel,
Button,
Grid,
Radio,
@@ -16,66 +12,14 @@ import {
FormControlLabel,
RadioGroup,
} from '@mui/material';
-
import { styled } from '@mui/material/styles';
-/** Project Form Component
- *
- * To be used for creating and updating a project
- * */
-
-const simpleInputs = [
- {
- label: 'Project Name',
- name: 'name',
- type: 'text',
- placeholder: 'Enter project name',
- },
- {
- label: 'Project Description',
- name: 'description',
- type: 'textarea',
- placeholder: 'Enter project description',
- },
- {
- label: 'Location',
- name: 'location',
- type: 'text',
- placeholder: 'Enter project location',
- },
- // Leaving incase we want to add this back in for updating projects
- // {
- // label: 'GitHub Identifier',
- // name: 'githubIdentifier',
- // type: 'text',
- // placeholder: 'Enter GitHub identifier',
- // },
- {
- label: 'GitHub URL',
- name: 'githubUrl',
- type: 'text',
- placeholder: 'htttps://github.com/',
- },
- {
- label: 'Slack Channel Link',
- name: 'slackUrl',
- type: 'text',
- placeholder: 'htttps://slack.com/',
- },
- {
- label: 'Google Drive URL',
- name: 'googleDriveUrl',
- type: 'text',
- placeholder: 'htttps://drive.google.com/',
- },
- // Leaving incase we want to add this back in for updating projects
- // {
- // label: 'HFLA Website URL',
- // name: 'hflaWebsiteUrl',
- // type: 'text',
- // placeholder: 'htttps://hackforla.org/projects/',
- // },
-];
+import useAuth from '../hooks/useAuth';
+import ProjectApiService from '../api/ProjectApiService';
+import { ReactComponent as EditIcon } from '../svg/Icon_Edit.svg';
+import { ReactComponent as PlusIcon } from '../svg/PlusIcon.svg';
+import ValidatedTextField from './parts/form/ValidatedTextField';
+import TitledBox from './parts/boxes/TitledBox';
/** STYLES
* -most TextField and InputLabel styles are controlled by the theme
@@ -101,80 +45,144 @@ const StyledRadio = styled(Radio)(({ theme }) => ({
/**Project Form Component
* -renders a form for creating and updating a project
- */
-
-export default function ProjectForm() {
- const [formData, setFormData] = useState({
- name: '',
- description: '',
- location: '',
- // githubIdentifier: '',
- githubUrl: '',
- slackUrl: '',
- googleDriveUrl: '',
- // hflaWebsiteUrl: '',
- });
-
- //seperate state for the location radio buttons
- const [locationType, setLocationType] = React.useState('remote');
- const [activeButton, setActiveButton] = React.useState('close');
- const [newlyCreatedID, setNewlyCreatedID] = useState(null);
+/**
+
+/**
+ * Takes Array, formData, projectToEdit, handleChage, isEdit
+ * submitForm, handleChange, and isEdit are for the edit forms.
+ * - arr - simpleInputs arr from the edit page that holds the input's properties.
+ * - formData - passes the current project information to the form.
+ * - projectToEdit - used to grab the of the project we are editing.
+ * - isEdit - Whether its creating a new project or editing one - True or False.
+ * - setFormData - allows us to updated the form data.
+ * */
+export default function ProjectForm({
+ arr,
+ formData,
+ projectToEdit,
+ isEdit,
+ setFormData
+}) {
const history = useHistory();
- const routeToNewProjectPage = () => {
- if(newlyCreatedID !== null) {
- history.push(`/projects/${newlyCreatedID}`)
- }
- }
-
-
- useEffect(() => {
- routeToNewProjectPage()
- },[newlyCreatedID])
+ // ----------------- States -----------------
+ const [locationType, setLocationType] = useState('remote');
+ // State to track the toggling from Project view to Edit Project View via edit icon.
+ const [editMode, setEditMode] = useState(false);
+ const { auth } = useAuth();
-
+ /**
+ * React Hook Forms
+ * - register
+ * - handleSubmit
+ * - formState
+ * - reset
+ * - defaultValues - holds edit project data
+ *
+ */
- // only handles radio button change
- const handleRadioChange = (event) => {
- setLocationType(event.target.value);
- };
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ } = useForm({
+ mode: 'all',
+ // Holds the current project data in state.
+ defaultValues: {
+ ...formData,
+ },
+ });
- //updates state of formData onChange of any form input
- const handleChange = (e) => {
- const { name, value } = e.target;
+ // ----------------- Submit requests -----------------
- setFormData((fData) => ({
- ...fData,
- [name]: value,
- }));
+ // Handles POST request found in api/ProjectApiService.
+ const submitNewProject = async (data) => {
+ const projectApi = new ProjectApiService();
+ try {
+ const id = await projectApi.create(data);
+ history.push(`/projects/${id}`);
+ } catch (errors) {
+ console.error(errors);
+ return;
+ }
};
- const handleSubmit = async (e) => {
- e.preventDefault();
+ // Fires PUT request to update the project,
+ const submitEditProject = async (data) => {
const projectApi = new ProjectApiService();
try {
- // fires POST request to create a new project,
- // but the server response does not include the newly created project id that we need
- const id = await projectApi.create(formData);
- setNewlyCreatedID(id);
+ await projectApi.updateProject(projectToEdit._id, data);
} catch (errors) {
console.error(errors);
return;
}
- setActiveButton('close');
+ // setOriginalProjectData(data);
+ setFormData(data);
+ setEditMode(false);
};
- // Basic validation : if all inputs have values, enable the submit button
- useEffect(() => {
- if (Object.values(formData).every((val) => val !== '')) {
- setActiveButton('save');
- } else {
- setActiveButton('close');
- }
- }, [formData]);
+ // ----------------- Handles and Toggles -----------------
+
+ // Handles the location radio button change.
+ const handleRadioChange = (event) => {
+ setLocationType(event.target.value);
+ };
+
+ // Toggles the project view to edit mode change.
+ const handleEditMode = (event) => {
+ setEditMode(!editMode);
+ // React hook form method to reset data back to original values. Triggered when Edit Mode is cancelled.
+ reset({
+ ...formData,
+ });
+ };
+ // ----------------- Icons -----------------
+
+ // Holds the Add New Project Icon and styling.
+ const addIcon = () => {
+ return (
+