diff --git a/.DS_Store b/.DS_Store index 208423c..7862d48 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.vscode/settings.json b/.vscode/settings.json index cf93286..9329ffb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, - "editor.formatOnSave": false, -} + "editor.formatOnSave":false, + "css.lint.unknownAtRules":"ignore", +} \ No newline at end of file diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js deleted file mode 100644 index a2ee21e..0000000 --- a/backend/.eslintrc.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - env: { - browser: true, - commonjs: true, - es2021: true, - }, - extends: 'google', - overrides: [ - { - env: { - node: true, - }, - files: [ - '.eslintrc.{js,cjs}', - ], - parserOptions: { - sourceType: 'script', - }, - }, - ], - parserOptions: { - ecmaVersion: 'latest', - }, - rules: { - 'require-jsdoc': 0, - }, -}; diff --git a/backend/controllers/AdminController.js b/backend/controllers/AdminController.js index 904f083..2c7284a 100644 --- a/backend/controllers/AdminController.js +++ b/backend/controllers/AdminController.js @@ -1,6 +1,9 @@ const express = require('express'); const db = require('../database'); +const bcrypt = require('bcryptjs'); +const {hash, genSalt} = require('bcryptjs'); + const getEventRequests = async (req, res, next) => { try { const requests = await db.manyOrNone( @@ -112,6 +115,62 @@ const processEventRequest = async (req, res, next) => { } } +// Middleware given an email in the body, retireves the given admin +// account or returns an error +const getAdminByEmail = async (req, res, next) => { + try { + const data = await db.oneOrNone('SELECT * FROM Admins WHERE email = $1', [ + req.body.email, + ]); + + if (data) { + res.locals.admin = data; + + next(); + } else { + res.status(401).json({message: 'Email not found.'}); + } + } catch (err) { + console.error(err); + res.status(500).json({error: 'Internal Server Error'}); + } +}; + +// Middleware to create a new admin account. +// If used, should be an admin route. i.e. allow Lluvia +// to create a new admin account if wanted. +const createAdmin = async (name, email, password) => { + const salt = await genSalt(10); + const hashed = await hash(password, salt); + + return await db.one( + 'INSERT INTO Admins (name, email, password) VALUES ($1, $2, $3) RETURNING *', + [name, email, hashed], + ); +}; + +const createAdminMiddleware = async (req, res, next) => { + try { + const {name, email, password} = req.body; + + if (name === undefined || email === undefined || password === undefined) { + console.log(req.body); + res.status(400).json({error: 'Missing required fields'}); + return; + } + + const data = await createAdmin(name, email, password); + + res.locals.data = data; + + next(); + } catch (err) { + console.error(err); + res.status(500).json({error: 'Internal Server Error'}); + return; + } +}; + module.exports = { getEventRequests, getAllEventRequests, @@ -120,4 +179,6 @@ module.exports = { createVendorViolation, deleteVendorViolation, processEventRequest, + getAdminByEmail, + createAdminMiddleware, }; \ No newline at end of file diff --git a/backend/controllers/AuthController.js b/backend/controllers/AuthController.js index c77cdfe..9c39a1c 100644 --- a/backend/controllers/AuthController.js +++ b/backend/controllers/AuthController.js @@ -1,16 +1,24 @@ const jwt = require('jsonwebtoken'); -// This middleware will be used to sign the token after a user logs in +const db = require('../database'); + +// TODO: Set cookies to secure when https is ready? + +// This middleware will be used to sign the token after a vendor logs in const signToken = async (req, res, next) => { + if (res.locals.vendor === undefined) { + return res.status(401).json({message: 'Unauthorized'}); + } + // Sign the token with JWT_SECRET - const token = jwt.sign(res.locals.vendor, process.env.JWT_SECRET); + const token = await jwt.sign(res.locals.vendor, process.env.JWT_SECRET); // Return the token in a cookie res.cookie('auth', token, {httpOnly: true, secure: false}); next(); }; -// This middleware will be used to verify the token sent by the client +// This middleware will be used to verify the token sent by the vendor const verifyToken = async (req, res, next) => { // Retrieve the token from the cookie const token = req.cookies.auth; @@ -20,11 +28,84 @@ const verifyToken = async (req, res, next) => { if (err) { res.status(401).json({message: 'Unauthorized'}); } else { - // Add the decoded object to res.locals - res.locals.vendor = decoded; - next(); + // Update cookie data from database + db.oneOrNone('SELECT * FROM vendors WHERE vendor_id = $1', [decoded.vendor_id], (result, err) => { + if (err) { + res.status(500).json({message: 'Internal Server Error'}); + } else if (result === null) { + res.status(401).json({message: 'Unauthorized'}); + } + + res.locals.vendor = result; + // Remove the password - isn't needed for any other processes. + delete res.locals.vendor['password']; + + // Sign new token and continue + signToken(req, res, next); + }); + } + }); +}; + +// This middleware will be used to sign the token after an admin logs in +const signAdminToken = async (req, res, next) => { + if (res.locals.admin === undefined) { + return res.status(401).json({message: 'Unauthorized'}); + } + + // Sign the token with JWT_SECRET + const token = await jwt.sign(res.locals.admin, process.env.JWT_SECRET); + + // Return the token in a cookie + res.cookie('auth_pim', token, {httpOnly: true, secure: false}); + + next(); +}; + +const verifyAdminToken = async (req, res, next) => { + // Retrieve the token from the cookie + const token = req.cookies.auth_pim; + + // Verify the token with JWT_SECRET + jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { + if (err) { + return res.status(401).json({message: 'Unauthorized'}); + } else { + // Keep the cookie up to date with the database + db.oneOrNone('SELECT * FROM admins WHERE admin_id = $1', [decoded.admin_id], (result, err) => { + if (err) { + console.log(err); + return res.status(500).json({message: 'Internal Server Error'}); + } else if (result === undefined) { + return res.status(401).json({message: 'Unauthorized'}); + } + + console.log(result); + + res.locals.admin = result; + delete res.locals.admin['password']; + + // Sign new token and continue + signAdminToken(req, res, next); + }); } }); }; -module.exports = {signToken, verifyToken}; +// Returns a middleware to verify the user has the correct permissions. +// Setting a route that only admins can access. +// USAGE: router.get('/route', verify('admin'), controller.method); +const verify = (privilege) => { + if (privilege === 'admin') { + // Check the admin token + return verifyAdminToken; + } else if (privilege === 'vendor') { + // Check the vendor token + return verifyToken; + } else { + // You shouldn't need to set privilege for any other use case. + throw new Error('Privilege should be either vendor or admin.'); + } +}; + +module.exports = {signToken, verifyToken, verifyAdminToken, signAdminToken, verify}; diff --git a/backend/controllers/EventController.js b/backend/controllers/EventController.js index 2f55026..daeca2a 100644 --- a/backend/controllers/EventController.js +++ b/backend/controllers/EventController.js @@ -37,12 +37,15 @@ const getEventById = async (req, res, next) => { const createEvent = async (req, res, next) => { const {name, location, datetime, description, capacity} = req.body; try { - await db.none(` + const event = await db.one(` INSERT INTO Events (name, location, datetime, description, vendor_capacity) VALUES ($1, $2, $3, $4, $5) RETURNING *; `, [name, location, datetime, description, capacity]); + // Returns the created event + res.locals.data = event; + next(); } catch (error) { console.error('Error creating event:', error); diff --git a/backend/controllers/VendorController.js b/backend/controllers/VendorController.js index 60d1add..67cb0dd 100644 --- a/backend/controllers/VendorController.js +++ b/backend/controllers/VendorController.js @@ -92,7 +92,7 @@ const getVendorById = async (req, res, next) => { // Registers the vendor in the database const createVendor = async (req, res, next) => { // Get the values from the request body - const {name, email, phone_number, password, website} = req.body; + const {name, email, phoneNumber, password, website} = req.body; // Checks if the required fields are present if (!password || !email || !name) { @@ -107,7 +107,7 @@ const createVendor = async (req, res, next) => { passwordHash = await hash(password, salt); } catch (err) { console.log(err); - res.status(500).json({error: err}); + res.status(495).json({error: err}); return; } @@ -121,7 +121,7 @@ const createVendor = async (req, res, next) => { password, \ website\ ) VALUES ($1, $2, $3, $4, $5)', - [name, email, phone_number, passwordHash, website], + [name, email, phoneNumber, passwordHash, website], ); } catch (err) { // Duplicate emails are not allowed diff --git a/backend/index.js b/backend/index.js index f3873df..09d28bf 100644 --- a/backend/index.js +++ b/backend/index.js @@ -28,6 +28,7 @@ app.use(cookieParser()); const VendorRouter = require('./routes/VendorRouter'); const AdminRouter = require('./routes/AdminRouter'); const EventRouter = require('./routes/EventRouter'); +const AdminRouter = require('./routes/AdminRouter'); app.use('/vendors', VendorRouter); app.use('/events', EventRouter); diff --git a/backend/middleware/successResponse.js b/backend/middleware/successResponse.js index 4426179..80188ab 100644 --- a/backend/middleware/successResponse.js +++ b/backend/middleware/successResponse.js @@ -1,6 +1,7 @@ // successResponse.js function sendSuccessResponse(req, res) { if (res.locals.data) { + // console.log('Data in res.locals', res.locals.data); res.status(200).json(res.locals.data); } else { // Fallback if no data is set in res.locals, but middleware is called diff --git a/backend/routes/AdminRouter.js b/backend/routes/AdminRouter.js index 3aadf1d..1a5168a 100644 --- a/backend/routes/AdminRouter.js +++ b/backend/routes/AdminRouter.js @@ -1,6 +1,14 @@ const express = require('express'); const router = express.Router(); +// Auth Controller Imports +const { + verifyAdminToken, + signAdminToken, + verify, +} = require('../controllers/AuthController'); + +// Admin Controller Imports const { getEventRequests, getAllEventRequests, @@ -9,6 +17,8 @@ const { createVendorViolation, deleteVendorViolation, processEventRequest, + getAdminByEmail, + createAdminMiddleware, } = require('../controllers/AdminController'); const sendSuccessResponse = require('../middleware/successResponse'); @@ -27,4 +37,14 @@ router.post('/violations/:vendorId', createVendorViolation, sendSuccessResponse) router.delete('/violations/:violationId', deleteVendorViolation, sendSuccessResponse); -module.exports = router; \ No newline at end of file +router.post('/login', getAdminByEmail, signAdminToken, (req, res) => { + res.status(200).json({status: 'success'}); +}); + +// UNFINISHED: Create an admin account +// Useful for creating an admin account for testing purposes. Password in database needs to be hashed for login to work properly. +// router.post('/', createAdminMiddleware, (req, res) => { +// res.status(200).json({status: 'success', admin: res.locals.data}); +// }); + +module.exports = router; diff --git a/backend/routes/EventRouter.js b/backend/routes/EventRouter.js index d0b306e..09f3cb2 100644 --- a/backend/routes/EventRouter.js +++ b/backend/routes/EventRouter.js @@ -7,6 +7,7 @@ const { updateEvent, } = require('../controllers/EventController'); const sendSuccessResponse = require('../middleware/successResponse'); +const {verify} = require('../controllers/AuthController'); // Get all events router.get('/', getAllEvents, sendSuccessResponse); @@ -15,9 +16,9 @@ router.get('/', getAllEvents, sendSuccessResponse); router.get('/:event_id', getEventById, sendSuccessResponse); // Create a new event -router.post('/', createEvent, sendSuccessResponse); +router.post('/', verify('admin'), createEvent, sendSuccessResponse); // Update an existing event -router.put('/:event_id', updateEvent, sendSuccessResponse); +router.put('/:event_id', verify('admin'), updateEvent, sendSuccessResponse); module.exports = router; diff --git a/backend/routes/VendorRouter.js b/backend/routes/VendorRouter.js index d7f0aad..8bc39b1 100644 --- a/backend/routes/VendorRouter.js +++ b/backend/routes/VendorRouter.js @@ -13,8 +13,8 @@ const { } = require('../controllers/VendorController'); const sendSuccessResponse = require('../middleware/successResponse'); const { - verifyToken, signToken, + verify, } = require('../controllers/AuthController'); // Logs in vendor @@ -32,16 +32,16 @@ router.post('/', createVendor, (req, res) => { }); // Create Vendor event request -router.post('/events/request', createEventRequest, sendSuccessResponse); +router.post('/events/request', verify('vendor'), createEventRequest, sendSuccessResponse); // Get Vendor event request -router.get('/events/request', getEventRequest, sendSuccessResponse); +router.get('/events/request', verify('admin'), getEventRequest, sendSuccessResponse); // Edit vendor by id // This probably should be an admin-protected route. How does that work? -router.put('/:vendorId', verifyToken, updateVendor, sendSuccessResponse); +router.put('/:vendorId', verify('admin'), updateVendor, sendSuccessResponse); // Route for vendor to update themself. ID is retrieved from the token. -router.put('/', verifyToken, updateAuthenticatedVendor, sendSuccessResponse); +router.put('/', verify('vendor'), updateAuthenticatedVendor, sendSuccessResponse); module.exports = router; diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 58beeac..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. - -The page will reload when you make changes.\ -You may also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index da95cfb..eebdd78 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "react-router": "^6.17.0", "react-router-dom": "^6.17.0", "react-scripts": "^5.0.1", + "schema-utils": "^4.2.0", "web-vitals": "^2.1.4" }, "devDependencies": { @@ -3330,6 +3331,23 @@ } } }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/@remix-run/router": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", @@ -6480,55 +6498,6 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7831,32 +7800,6 @@ "webpack": "^5.0.0" } }, - "node_modules/eslint-webpack-plugin/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/eslint-webpack-plugin/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7878,29 +7821,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/eslint-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8324,6 +8244,23 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -12480,55 +12417,6 @@ "webpack": "^5.0.0" } }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -15493,22 +15381,54 @@ } }, "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -16567,6 +16487,23 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -17214,55 +17151,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpack-dev-server": { "version": "4.15.1", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", @@ -17321,55 +17209,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpack-dev-server/node_modules/ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", @@ -17453,6 +17292,23 @@ "node": ">=4.0" } }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 62d37ed..8fa73fb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "react-router": "^6.17.0", "react-router-dom": "^6.17.0", "react-scripts": "^5.0.1", + "schema-utils": "^4.2.0", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/frontend/src/App.css b/frontend/src/App.css index 5d09417..bd6213e 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,8 +1,3 @@ @tailwind base; @tailwind components; -@tailwind utilities; - - - - - +@tailwind utilities; \ No newline at end of file diff --git a/frontend/src/alert.jsx b/frontend/src/alert.jsx index 1817625..532a35e 100644 --- a/frontend/src/alert.jsx +++ b/frontend/src/alert.jsx @@ -18,8 +18,8 @@ export const Alert = ({content, bad}) => { return ( <> { bad ? -
{content}
: -
{content}
+
{content}
: +
{content}
} diff --git a/frontend/src/components/footer.jsx b/frontend/src/components/footer.jsx index de03e95..4feb9a1 100644 --- a/frontend/src/components/footer.jsx +++ b/frontend/src/components/footer.jsx @@ -7,7 +7,7 @@ import {faSquareInstagram} from '@fortawesome/free-brands-svg-icons'; export default function Footer() { return ( -