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 ( + + + + Add New Project + + + ); + }; + // Holds the Edit New Project Icon and styling. + const editIcon = () => { + return ( + + + + {editMode ? 'Cancel' : 'Edit Mode'} + + + ); + }; + + // ----------------- Location radio ----------------- + + // Holdes the location radios styling. const locationRadios = ( @@ -190,110 +198,76 @@ export default function ProjectForm() { value="remote" control={} label="Remote" + disabled={isEdit ? !editMode : false} /> } label="In-Person" + disabled={isEdit ? !editMode : false} /> ); - return ( + return auth && auth.user ? ( Project Management - - - - - Project Information - - - - - - Add New Project - - - - - -
- {simpleInputs.map((input) => ( - - - - - {input.label} - - - - {input.name === 'location' && locationRadios} - - - - - ))} -
-
-
- - - - - Save - - - - - Close - + +
{ + isEdit ? submitEditProject(data) : submitNewProject(data); + })} + > + {arr.map((input) => ( + + ))} + + + + + + Save + + + + + Close + + -
-
+
+ + ) : ( + ); } diff --git a/client/src/components/auth/HandleAuth.js b/client/src/components/auth/HandleAuth.js index ac142f6da..4c9d6abb4 100644 --- a/client/src/components/auth/HandleAuth.js +++ b/client/src/components/auth/HandleAuth.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Redirect } from 'react-router-dom'; import { isValidToken } from '../../services/user.service'; -import {authLevelRedirect} from '../../utils/authUtils' +import { authLevelRedirect } from '../../utils/authUtils'; import '../../sass/MagicLink.scss'; import useAuth from '../../hooks/useAuth'; @@ -17,47 +17,59 @@ const HandleAuth = (props) => { const params = new URLSearchParams(search); const api_token = params.get('token'); - if(!api_token) return; + if (!api_token) return; isValidToken(api_token).then((isValid) => { - setMagicLink(isValid) + setMagicLink(isValid); }); }, [props.location.search]); // Step 2: Refresh user auth (requires valid Magic Link) useEffect(() => { - if(!isMagicLinkValid) return; - if(!auth?.isError) return; + if (!isMagicLinkValid) return; + if (!auth?.isError) return; refreshAuth(); - },[isMagicLinkValid, refreshAuth, auth]) + }, [isMagicLinkValid, refreshAuth, auth]); // Step 3: Set IsLoaded value to render Component useEffect(() => { - if(!isMagicLinkValid) { + if (!isMagicLinkValid) { setIsLoaded(true); return; - }; + } + + if (!auth || auth.isError) return; - if(!auth || auth.isError) return; - setIsLoaded(true); - },[isMagicLinkValid, setIsLoaded, auth]) + }, [isMagicLinkValid, setIsLoaded, auth]); + + if (!isLoaded) return
Loading...
; + + const Delayed = ({ children, waitBeforeShow = 500 }) => { + const [isShown, setIsShown] = useState(false); + useEffect(() => { + const timer = setTimeout(() => { + setIsShown(true); + }, waitBeforeShow); + + return () => clearTimeout(timer); + }, [waitBeforeShow]); + + return isShown ? children : null; + }; - if(!isLoaded) - return ( -
Loading...
- ); + let loginRedirect = ''; if (auth?.user) { - const loginRedirect = authLevelRedirect(auth.user); - return ( - - ); + loginRedirect = authLevelRedirect(auth.user); } return (
-
Sorry, the link is not valid anymore.
+ +
Sorry, the link is not valid anymore.
+
+ {auth?.user && /* Redirect to /welcome */}
); }; diff --git a/client/src/components/data.js b/client/src/components/data.js new file mode 100644 index 000000000..ca37540ba --- /dev/null +++ b/client/src/components/data.js @@ -0,0 +1,103 @@ +// Array filled with default inputs. +export const simpleInputs = [ + { + label: 'Project Name', + name: 'name', + type: 'text', + placeholder: 'Enter project name', + disabled: false + }, + { + label: 'Project Description', + name: 'description', + type: 'textarea', + placeholder: 'Enter project description', + value: /^[a-zA-Z0-9].{0,250}$/, + errorMessage: 'Description must start with alphanumeric characters, 250 char limit', + disabled: false + }, + { + label: 'Location', + name: 'location', + type: 'text', + placeholder: 'Enter location for meeting', + value: /https:\/\/[\w-]*\.?zoom.us\/(j|my)\/[\d\w?=-]+/, + errorMessage: 'Please enter a valid Zoom URL', + addressValue: '', + addressError: 'Invalid address', + disabled: false + }, + { + label: 'GitHub Identifier', + name: 'githubIdentifier', + type: 'text', + placeholder: 'Enter GitHub identifier', + disabled: false + }, + { + label: 'GitHub URL', + name: 'githubUrl', + type: 'text', + placeholder: 'htttps://github.com/', + disabled: false + }, + { + label: 'Slack Channel Link', + name: 'slackUrl', + type: 'text', + placeholder: 'htttps://slack.com/', + disabled: false + }, + { + label: 'Google Drive URL', + name: 'googleDriveUrl', + type: 'text', + placeholder: 'htttps://drive.google.com/', + disabled: false + }, + { + label: 'HFLA Website URL', + name: 'hflaWebsiteUrl', + type: 'text', + placeholder: 'htttps://hackforla.org/projects/', + disabled: false + }, +]; + +export const additionalInputsForEdit = [ + { + label: 'Partners', + name: 'partners', + type: 'text', + placeholder: 'partners', + disabled: false + }, + { + label: 'Managed by Users', + name: 'managedByUsers', + type: 'text', + placeholder: 'Managed by Users', + disabled: false + }, + { + label: 'Project Status', + name: 'projectStatus', + type: 'text', + placeholder: 'Project Status', + disabled: false + }, + { + label: 'Google Drive ID', + name: 'googleDriveId', + type: 'text', + placeholder: 'htttps://drive.google.com/', + disabled: false + }, + { + label: 'Created Date', + name: 'createdDate', + type: 'text', + placeholder: 'Created Date', + disabled: true + } +] \ No newline at end of file diff --git a/client/src/components/manageProjects/addProject.js b/client/src/components/manageProjects/addProject.js new file mode 100644 index 000000000..ddec883be --- /dev/null +++ b/client/src/components/manageProjects/addProject.js @@ -0,0 +1,18 @@ +import React from 'react'; +import ProjectForm from '../ProjectForm'; +import { simpleInputs } from '../data'; + +function addProject() { + return ( +
+ +
+ ); +} + +export default addProject; diff --git a/client/src/components/manageProjects/createNewEvent.js b/client/src/components/manageProjects/createNewEvent.js index f29f250fc..04df044b3 100644 --- a/client/src/components/manageProjects/createNewEvent.js +++ b/client/src/components/manageProjects/createNewEvent.js @@ -7,7 +7,7 @@ import validateEventForm from './utilities/validateEventForm'; import EventForm from './eventForm'; const CreateNewEvent = ({ - projectName, + projectToEdit, projectID, createNewRecurringEvent, setEventAlert, @@ -72,7 +72,7 @@ const CreateNewEvent = ({ // Handle submission of new recurring event form const handleFormSubmit = async () => { - const errors = validateEventForm(formValues); + const errors = validateEventForm(formValues, projectToEdit); if (!errors) { handleEventCreate(); setFormValues(initialFormValues); diff --git a/client/src/components/manageProjects/editMeetingTimes.js b/client/src/components/manageProjects/editMeetingTimes.js index 7c27edbd9..67beceab0 100644 --- a/client/src/components/manageProjects/editMeetingTimes.js +++ b/client/src/components/manageProjects/editMeetingTimes.js @@ -8,6 +8,7 @@ import validateEventForm from './utilities/validateEventForm'; // This component displays current meeting times for selected project and offers the option to edit those times. const EditMeetingTimes = ({ + projectToEdit, selectedEvent, setEventAlert, setSelectedEvent, @@ -21,7 +22,7 @@ const EditMeetingTimes = ({ startTimeOriginal, durationOriginal ) => async () => { - const errors = validateEventForm(values); + const errors = validateEventForm(values, projectToEdit); if (!errors) { let theUpdatedEvent = {}; diff --git a/client/src/components/manageProjects/editProject.js b/client/src/components/manageProjects/editProject.js index 21d7f5798..886d0a229 100644 --- a/client/src/components/manageProjects/editProject.js +++ b/client/src/components/manageProjects/editProject.js @@ -1,34 +1,66 @@ import React, { useState, useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import EditableField from './editableField'; import EditMeetingTimes from './editMeetingTimes'; import CreateNewEvent from './createNewEvent'; import readableEvent from './utilities/readableEvent'; -import { ReactComponent as EditIcon } from "../../svg/Icon_Edit.svg"; -import '../../sass/ManageProjects.scss'; +import ProjectForm from '../ProjectForm'; +import { simpleInputs, additionalInputsForEdit } from '../data'; +import TitledBox from '../parts/boxes/TitledBox'; + +import { ReactComponent as EditIcon } from '../../svg/Icon_Edit.svg'; +import { ReactComponent as PlusIcon } from '../../svg/PlusIcon.svg'; + +import { Typography, Box } from '@mui/material'; // Need to hold user state to check which type of user they are and conditionally render editing fields in this component // for user level block access to all except for the ones checked const EditProject = ({ projectToEdit, - userAccessLevel, - updateProject, recurringEvents, createNewRecurringEvent, deleteRecurringEvent, updateRecurringEvent, + regularEvents, + updateRegularEvent, }) => { - // Add commas to arrays for display - const partnerDataFormatted = projectToEdit.partners.join(', '); + const [formData, setFormData] = useState({ + name: projectToEdit.name, + description: projectToEdit.description, + location: projectToEdit.location, + githubIdentifier: projectToEdit.githubIdentifier, + githubUrl: projectToEdit.githubUrl, + slackUrl: projectToEdit.slackUrl, + googleDriveUrl: projectToEdit.googleDriveUrl, + hflaWebsiteUrl: projectToEdit.hflaWebsiteUrl, + partners: projectToEdit.partners, + managedByUsers: projectToEdit.managedByUsers, + projectStatus: projectToEdit.projectStatus, + googleDriveId: projectToEdit.googleDriveId, + createdDate: new Date(projectToEdit.createdDate) + }); + // eslint-disable-next-line no-unused-vars - const recrutingDataFormatted = projectToEdit.recruitingCategories.join(', '); + const [rEvents, setREvents] = useState([]); + const [regularEventsState, setRegularEventsState] = useState([]); const [selectedEvent, setSelectedEvent] = useState(); const [isCreateNew, setIsCreateNew] = useState(); // States for alerts const [eventAlert, setEventAlert] = useState(null); + // test + useEffect(() => { + if (regularEvents) { + setRegularEventsState( + regularEvents + // eslint-disable-next-line no-underscore-dangle + .filter((e) => e?.project?._id === projectToEdit._id) + .map((item) => ({ ...item, ...readableEvent(item), raw: item })) + .reverse() // sorts most recent events first + ); + } + }, [projectToEdit, regularEvents, setRegularEventsState]); + // Get project recurring events when component loads useEffect(() => { if (recurringEvents) { @@ -43,9 +75,10 @@ const EditProject = ({ }, [projectToEdit, recurringEvents, setREvents]); return ( -
+
- - All Projects - -
- *The data here is only test data and is not accurate* -
-
{`Project: ${projectToEdit.name}`}
- - - - - - - - {/* */} - - {/* */} - {/* */} - - {/* */} -
-

Recurring Events

-

{eventAlert}

-
    - {rEvents.map((event) => ( - // eslint-disable-next-line no-underscore-dangle -
  • - -
  • - ))} -
-
-
- -
-
+ + setIsCreateNew(true)} + > + + + Add New Event + + + } + > +
+

{eventAlert}

+
    + {rEvents.map((event) => ( + // eslint-disable-next-line no-underscore-dangle +
  • + +
  • + ))} +
+
+
+ + +
+

{eventAlert}

+
    + {regularEventsState.map((event, index) => ( + + // eslint-dis able-next-line no-underscore-dangle + + ))} +
+
+
+ ); }; +function RegularEvent({ event, updateRegularEvent }) { + return ( +
  • + +
  • + ) +} + + + export default EditProject; diff --git a/client/src/components/manageProjects/utilities/validateEditableField.js b/client/src/components/manageProjects/utilities/validateEditableField.js new file mode 100644 index 000000000..b4a68a4dd --- /dev/null +++ b/client/src/components/manageProjects/utilities/validateEditableField.js @@ -0,0 +1,38 @@ +export const validateEditableField = (fieldName, fieldValue) => { + switch (fieldName) { + case 'hflaWebsiteUrl': + return doesLinkContainFlex(fieldValue, 'hackforla.org'); + case 'slackUrl': + return doesLinkContainFlex(fieldValue, 'slack.com'); + case 'googleDriveUrl': + return doesLinkContainFlex(fieldValue, 'drive.google.com'); + case 'githubUrl': + return doesLinkContainFlex(fieldValue, 'github.com'); + case 'description': + return typeof fieldValue === 'string' && fieldValue.length <= 250; + default: + break; + } + return true; +}; + +export const generateErrorEditableField = (fieldName) => { + switch (fieldName) { + case 'hflaWebsiteUrl': + case 'slackUrl': + case 'googleDriveUrl': + case 'githubUrl': + return `Invalid field value for ${fieldName}`; + case 'description': + return 'Description is too long, max 250 characters allowed'; + default: + break; + } +}; + +const doesLinkContainFlex = (link, key) => { + if (link.startsWith(`https://${key}`)) return true; + if (link.startsWith(`https://www.${key}`)) return true; + if (link.startsWith(key)) return true; + return false; +}; diff --git a/client/src/components/manageProjects/utilities/validateEventForm.js b/client/src/components/manageProjects/utilities/validateEventForm.js index 99eafc3ce..6128e1b03 100644 --- a/client/src/components/manageProjects/utilities/validateEventForm.js +++ b/client/src/components/manageProjects/utilities/validateEventForm.js @@ -1,6 +1,7 @@ -import validator from 'validator' +import validator from 'validator'; +import { isWordInArrayInString } from './../../../utils/stringUtils.js'; -const validateEventForm = (vals) => { +const validateEventForm = (vals, projectToEdit) => { let newErrors = {}; Object.keys(vals).forEach((key) => { switch (key) { @@ -8,6 +9,30 @@ const validateEventForm = (vals) => { // Required if (!vals[key]) { newErrors = { ...newErrors, name: 'Event name is required' }; + } else if ( + isWordInArrayInString( + ['meeting', 'mtg'], + vals[key].toLowerCase() + ) + ) { + newErrors = { + ...newErrors, + name: "Event name cannot contain 'meeting' or 'mtg'", + }; + } else if ( + isWordInArrayInString( + [projectToEdit.name.toLowerCase()], + vals[key].toLowerCase() + ) + ) { + if (projectToEdit.name.toLowerCase() === 'onboarding') { + // Do nothing, word `onboarding` has been white-listed + } else { + newErrors = { + ...newErrors, + name: `Event name cannot contain the Project Name: '${projectToEdit.name}'`, + }; + } } break; @@ -22,8 +47,8 @@ const validateEventForm = (vals) => { if (!validateLink(vals[key])) { newErrors = { ...newErrors, - videoConferenceLink: 'Invalid link' - } + videoConferenceLink: 'Invalid link', + }; } break; diff --git a/client/src/components/parts/boxes/TitledBox.js b/client/src/components/parts/boxes/TitledBox.js new file mode 100644 index 000000000..3d248991d --- /dev/null +++ b/client/src/components/parts/boxes/TitledBox.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { Box, Typography, Divider } from '@mui/material'; + +export default function TitledBox({ title, children, badge, childrenBoxSx }) { + return ( + + + + + {title} + + + {badge} + + + {children} + + ); + + +} \ No newline at end of file diff --git a/client/src/components/parts/form/ValidatedTextField.js b/client/src/components/parts/form/ValidatedTextField.js new file mode 100644 index 000000000..1e8bc96d5 --- /dev/null +++ b/client/src/components/parts/form/ValidatedTextField.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { Box, Grid, InputLabel, TextField } from "@mui/material"; + +/** + * A validated text field component for forms. + * + * @component + * @param {Object} props - The component props. + * @param {Function} props.register - Function used for registering the input field with react-hook-form. + * @param {Object} props.errors - Object containing form validation errors. + * @param {boolean} props.isEdit - Boolean indicating if the form is in edit mode. + * @param {boolean} props.editMode - Boolean indicating if the component is in edit mode. + * @param {string} props.locationType - The type of location. + * @param {ReactNode} props.locationRadios - Radio Buttons for selecting location type. + * @param {Object} props.input - The input configuration for the text field. + * @returns {ReactElement} The rendered component. + */ +function ValidatedTextField({ + register, + errors, + isEdit, + editMode, + locationType, + locationRadios, + input, +}) { + const registerObj = { + ...register(input.name, { + required: `${input.name} is required`, + pattern: + input.name === 'location' + ? locationType === 'remote' + ? { + value: input.value, + message: input.errorMessage, + } + : { + value: input.addressValue, + message: input.addressError, + } + : { value: input.value, message: input.errorMessage }, + } + )} + + return ( + + + + + {input.label} + + + {input.name === 'location' && locationRadios} + + + + ); +}; + +export default ValidatedTextField; \ No newline at end of file diff --git a/client/src/components/user-admin/EditUsers.js b/client/src/components/user-admin/EditUsers.js index 1c4fc3b29..1e5f72e93 100644 --- a/client/src/components/user-admin/EditUsers.js +++ b/client/src/components/user-admin/EditUsers.js @@ -1,10 +1,12 @@ import React, { useEffect, useState } from 'react'; import '../../sass/UserAdmin.scss'; +import { FormGroup, FormControlLabel, Switch } from '@mui/material' // child of UserAdmin. Displays form to update users. -const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects }) => { +const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects, updateUserActiveStatus }) => { const [userManagedProjects, setUserManagedProjects] = useState([]); // The projects that the selected user is assigned const [projectValue, setProjectValue] = useState(''); // State and handler for form in EditUsers + const [isActive, setIsActive] = useState(userToEdit.isActive); // Prepare data for display const userName = `${userToEdit.name?.firstName} ${userToEdit.name?.lastName}`; @@ -57,6 +59,11 @@ const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects }) => { } }; + const handleSetIsActive = () => { + setIsActive(!isActive) + updateUserActiveStatus(userToEdit, !isActive) + } + return (
    @@ -67,6 +74,15 @@ const EditUsers = ({ userToEdit, backToSearch, updateUserDb, projects }) => {
    Email:
    {userEmail}
    +
    +
    Is Active:
    +
    + {isActive.toString()} + + } onClick={() => handleSetIsActive()} /> + +
    +
    Projects:
    diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index e64a6f4bd..48e1df04e 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -49,7 +49,7 @@ const Home = () => {

    Volunteer Relationship Management System

    - {events && events.length >= 1 && ( + {events && events.length > 0 ? (
    {
    - )} + ):(
    {/* If no events with checkInReady: true */} - {events.length === 0 && } - + {/* If no meetings available*/} +

    No meetings available

    + +
    + )} {/* If any events with checkInReady: true */} {events.length > 0 && ( - +
    + +
    )} -
    ); }; diff --git a/client/src/pages/ManageProjects.js b/client/src/pages/ManageProjects.js index 577a7a495..add89f591 100644 --- a/client/src/pages/ManageProjects.js +++ b/client/src/pages/ManageProjects.js @@ -7,6 +7,7 @@ import ProjectApiService from '../api/ProjectApiService'; import RecurringEventsApiService from '../api/RecurringEventsApiService'; import Loading from '../svg/22.gif'; import '../sass/ManageProjects.scss'; +import EventsApiService_ from '../api/EventsApiService'; const PAGES = Object.freeze({ selectProject: 'selectProject', @@ -20,21 +21,30 @@ const ManageProjects = () => { const [projects, setProjects] = useState(); const [projectToEdit, setProjectToEdit] = useState(); const [recurringEvents, setRecurringEvents] = useState(); + const [regularEvents, setRegularEvents] = useState([]); const [componentToDisplay, setComponentToDisplay] = useState(''); const [projectApiService] = useState(new ProjectApiService()); const [recurringEventsApiService] = useState(new RecurringEventsApiService()); + const [EventsApiService] = useState(new EventsApiService_()); const [projectsLoading, setProjectsLoading] = useState(false); const [eventsLoading, setEventsLoading] = useState(false); const user = auth?.user; - const fetchProjects = useCallback(async () => { + const fetchProjects = useCallback(async () => { setProjectsLoading(true); const projectRes = await projectApiService.fetchProjects(); setProjects(projectRes); setProjectsLoading(false); }, [projectApiService]); + const fetchRegularEvents = useCallback(async () => { + setEventsLoading(true); + const eventsRes = await EventsApiService.fetchEvents(); + setRegularEvents(eventsRes); + setEventsLoading(false); + }, [recurringEventsApiService]); + const fetchRecurringEvents = useCallback(async () => { setEventsLoading(true); const eventsRes = await recurringEventsApiService.fetchRecurringEvents(); @@ -82,6 +92,17 @@ const ManageProjects = () => { [recurringEventsApiService, fetchRecurringEvents] ); + const updateRegularEvent = useCallback( + async (eventToUpdate, eventId) => { + await EventsApiService.updateEvent( + eventToUpdate, + eventId + ); + fetchRegularEvents(); + }, + [recurringEventsApiService, fetchRegularEvents] + ); + useEffect(() => { // Refresh project to edit, if projects have been refreshed if (projectId && projects) { @@ -98,7 +119,8 @@ const ManageProjects = () => { useEffect(() => { fetchProjects(); fetchRecurringEvents(); - }, [fetchProjects, fetchRecurringEvents]); + fetchRegularEvents(); + }, [fetchProjects, fetchRecurringEvents, fetchRegularEvents]); // If not logged in, redirect to login page if (!auth && !auth?.user) { @@ -118,6 +140,8 @@ const ManageProjects = () => { createNewRecurringEvent={createNewRecurringEvent} deleteRecurringEvent={deleteRecurringEvent} updateRecurringEvent={updateRecurringEvent} + regularEvents={regularEvents} + updateRegularEvent={updateRegularEvent} /> ); break; diff --git a/client/src/pages/ProjectList.js b/client/src/pages/ProjectList.js index fb14d1593..79e731f07 100644 --- a/client/src/pages/ProjectList.js +++ b/client/src/pages/ProjectList.js @@ -8,10 +8,10 @@ import { Box, CircularProgress, Typography, - Divider, Button, } from '@mui/material'; import { Link } from 'react-router-dom'; +import TitledBox from '../components/parts/boxes/TitledBox'; const StyledTypography = styled(Typography)({ textTransform: 'uppercase', @@ -98,12 +98,7 @@ export default function ProjectList() { )} - - - Active Projects - - - + {projects.map((project) => ( ))} - - + ); } diff --git a/client/src/pages/UserAdmin.js b/client/src/pages/UserAdmin.js index 0edb2241a..c540082ff 100644 --- a/client/src/pages/UserAdmin.js +++ b/client/src/pages/UserAdmin.js @@ -30,6 +30,13 @@ const UserAdmin = () => { [userApiService, fetchUsers] ); + const updateUserActiveStatus = useCallback( + async (user, isActive) => { + await userApiService.updateUserDbIsActive(user, isActive); + fetchUsers() + }, [userApiService, fetchUsers] + ) + const fetchProjects = useCallback(async () => { const projectRes = await projectApiService.fetchProjects(); setProjects(projectRes); @@ -57,6 +64,7 @@ const UserAdmin = () => { projects={projects} updateUserDb={updateUserDb} backToSearch={backToSearch} + updateUserActiveStatus={updateUserActiveStatus} /> ); } diff --git a/client/src/sass/ManageProjects.scss b/client/src/sass/ManageProjects.scss index 67292c383..46899a9dc 100644 --- a/client/src/sass/ManageProjects.scss +++ b/client/src/sass/ManageProjects.scss @@ -139,7 +139,6 @@ div.editable-field { ul { padding-top: 4px; - border-top: 2px solid #32373b; } li { diff --git a/client/src/sass/UserAdmin.scss b/client/src/sass/UserAdmin.scss index 67422f86d..373cf7d34 100644 --- a/client/src/sass/UserAdmin.scss +++ b/client/src/sass/UserAdmin.scss @@ -64,6 +64,22 @@ margin-bottom: 8px; } +.user-is-active-column-left { + display: flex; + flex-direction: column; + flex-basis: 15%; +} + +.active-status { + margin-right: 15px; + width: 25px; +} + +.is-active-flex { + display: flex; + align-items: center; +} + .button-remove { width: 25%; color: #fa114f; diff --git a/client/src/svg/Icon_Edit.svg b/client/src/svg/Icon_Edit.svg index f2eb7335c..6a6271821 100644 --- a/client/src/svg/Icon_Edit.svg +++ b/client/src/svg/Icon_Edit.svg @@ -1,3 +1,3 @@ - + diff --git a/client/src/theme/index.js b/client/src/theme/index.js index 75df741df..095beac8a 100644 --- a/client/src/theme/index.js +++ b/client/src/theme/index.js @@ -48,7 +48,7 @@ let theme = createTheme({ MuiTextField: { defaultProps: { inputProps: { - style: { width: '100%', color: uiKitColors.black, border: 'none' }, + style: { width: '100%', color: uiKitColors.black, border: 'none', paddingLeft: '0.5em' }, }, fullWidth: true, }, diff --git a/client/src/utils/stringUtils.js b/client/src/utils/stringUtils.js new file mode 100644 index 000000000..987e632ba --- /dev/null +++ b/client/src/utils/stringUtils.js @@ -0,0 +1,9 @@ +export const isWordInArrayInString = (arr, str) => { + const words = str.split(' '); + for (let word of words) { + if (arr.includes(word)) { + return true; + } + } + return false; +}; diff --git a/client/yarn.lock b/client/yarn.lock index a862e5300..4d56d2a43 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -24,6 +24,14 @@ dependencies: "@babel/highlight" "^7.18.6" +"@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.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.9.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" @@ -72,7 +80,7 @@ json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.21.0", "@babel/generator@^7.21.1", "@babel/generator@^7.4.0", "@babel/generator@^7.9.0": +"@babel/generator@^7.21.0", "@babel/generator@^7.4.0", "@babel/generator@^7.9.0": version "7.21.1" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd" integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== @@ -82,6 +90,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@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-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -147,6 +165,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@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-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -162,6 +185,14 @@ "@babel/template" "^7.20.7" "@babel/types" "^7.21.0" +"@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/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -169,6 +200,13 @@ dependencies: "@babel/types" "^7.18.6" +"@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.22.5" + "@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" @@ -252,16 +290,33 @@ dependencies: "@babel/types" "^7.18.6" +"@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.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@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.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@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.18.6": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" @@ -295,11 +350,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.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.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.0": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== +"@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-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1184,19 +1253,28 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75" - integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw== +"@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.18.6" - "@babel/generator" "^7.21.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.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" @@ -1209,6 +1287,15 @@ "@babel/helper-validator-identifier" "^7.19.1" 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" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -3441,7 +3528,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1: +bn.js@^5.0.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -3560,7 +3647,7 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== @@ -3569,19 +3656,19 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + version "4.2.2" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" + integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" + bn.js "^5.2.1" + browserify-rsa "^4.1.0" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.3" + elliptic "^6.5.4" inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" + parse-asn1 "^5.1.6" + readable-stream "^3.6.2" + safe-buffer "^5.2.1" browserify-zlib@^0.2.0: version "0.2.0" @@ -5265,7 +5352,7 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.4.284: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.320.tgz#4d83a90ff74f93939c5413c2ac5a16c696600632" integrity sha512-h70iRscrNluMZPVICXYl5SSB+rBKo22XfuIS1ER0OQxQZpKTnFpuS6coj7wY9M/3trv7OR88rRMOlKmRvDty7Q== -elliptic@^6.5.3: +elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -9286,7 +9373,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0, parse-asn1@^5.1.5: +parse-asn1@^5.0.0, parse-asn1@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== @@ -10732,6 +10819,15 @@ readable-stream@^3.0.6, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -11137,7 +11233,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -12816,9 +12912,9 @@ which@^2.0.1: isexe "^2.0.0" 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== workbox-background-sync@^4.3.1: version "4.3.1"