From 0d903f69528f61cf07d15925d5c05397078db138 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 15 May 2025 16:54:01 +0800 Subject: [PATCH 01/18] add value and onchange in the signup --- backend/src/controllers/auth.controller.js | 8 +- backend/src/models/employee.model.js | 2 +- frontend/src/pages/Signup.jsx | 97 ++++++++++++++++------ 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index 8c97cdc..ecd121f 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -56,7 +56,7 @@ export const signup = async(req,res,next) =>{ fname, lname, mname, - sex, + sex: sex.toLowerCase(), birthdate, email: email.toLowerCase(), password: hashedPassword, @@ -77,6 +77,7 @@ export const signup = async(req,res,next) =>{ return res.status(201).json({ message: "Employee created successfully", employee:{ + emp_id: newEmployee.emp_id, id: newEmployee.id, fname: newEmployee.fname, lname: newEmployee.lname, @@ -167,6 +168,9 @@ export const logout = async(req,res) =>{ res.status(200).json({message: "Logged out successfully"}); } catch (error) { - console.log() + console.log(error); + res.status(500).json({message: "Internal server error (Logout)"}); + + } } \ No newline at end of file diff --git a/backend/src/models/employee.model.js b/backend/src/models/employee.model.js index 32628d1..4affb73 100644 --- a/backend/src/models/employee.model.js +++ b/backend/src/models/employee.model.js @@ -58,7 +58,7 @@ const Employee = db.define('employee',{ allowNull: false, }, role:{ - type: DataTypes.ENUM('admin', 'employee'), + type: DataTypes.ENUM('admin', 'staff'), allowNull: false, }, position:{ diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 3a06acf..7a82b91 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -66,7 +66,6 @@ const handleChange = (e) =>{ - //concat the address const {address1, address2, address3, ...otherData} = formData; const mergingAddress = [address1, address2, address3].filter(Boolean); @@ -93,12 +92,11 @@ const handleChange = (e) =>{ if(response.ok){ toast.success("Signup succesfully"); } else { - alert(`Signup failed: ${result.message || "Unknown error"}`) - toast.success(`Signup failed: ${result.message || "Unknown error"}`); + toast.error(`Signup failed: ${result.message || "Unknown error"}`); } } catch (error) { console.error("Error during signup:", error); - alert("An error occurred during signup."); + toast.error("An error occurred during signup."); } @@ -129,6 +127,7 @@ const handleChange = (e) =>{ className="flex justify-end ml-[45rem] border-black shadow-sm border-1 text-gray-700" placeholder=" Auto-Generated" + //tatanggalin value={formData.emp_id} onChange={handleChange} disabled @@ -193,12 +192,22 @@ const handleChange = (e) =>{ id="birthday" name="birthday" className="border-black border-1 shadow-sm text-gray-700 p-1" + + + value={formData.birthdate} + onChange={handleChange} + required /> + + value={formData.role} + > - - - + +
@@ -226,14 +240,26 @@ const handleChange = (e) =>{ Address - @@ -241,26 +267,37 @@ const handleChange = (e) =>{ - -
@@ -278,6 +315,10 @@ const handleChange = (e) =>{ name="email" className="border-black border-1 shadow-sm text-gray-700 p-1 w-[25rem]" placeholder="Enter Email Address" + + value={formData.email} + onChange={handleChange} + required /> { name="phoneNumber" className="border-black border-1 shadow-sm text-gray-700 p-1 w-[25rem]" placeholder="Enter Phone Number" + + + value={formData.phoneNumber} + onChange={handleChange} />
From 4777f5361f45193f30865f53f699262614cc76ad Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 19 May 2025 16:54:43 +0800 Subject: [PATCH 02/18] still coding on the signup --- frontend/src/pages/Signup.jsx | 208 ++++++++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 46 deletions(-) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 7a82b91..5799c96 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -27,7 +27,15 @@ function Signup() { //picture profileImage: null, + + + + }); + //password + const [showPasswordDialog, setPasswordDialog] = useState(false); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); const handleChange = (e) =>{ @@ -35,6 +43,102 @@ const handleChange = (e) =>{ setFormData((prevData) =>({...prevData, [name]: value})); }; +const handlePasswordChange = (e) => setPassword(e.target.value); +const handleConfirmPasswordChange = (e) => setConfirmPassword(e.target.value); + +const validateMainFormData = () =>{ + const requiredFields = { + + fname: true, + mname: true, + lname: true, + birthdate: true, + sex: true, + position: true, + role: true, + address1: true, + email: true, + }; + + + //may babaguhin pa + const fieldDisplayNames = { + + fname: "First Name", + mname: "Middle Name", + lname: "Last Name", + birthdate: "Birthday", + sex: "Sex", + position: "Position", + role: "Role", + address1: "Address Line 1", + email: "Email", + }; + + + + + + +for (const field in requiredFields){ + if(requiredFields[field] && !formData[field]){ + toast.error(`${fieldDisplayNames[field]} is required.`); + return false; + } +} +if(formData.email && !/\S+@\S+\.\S+/.test(formData.email)){ + toast.error("Please enter a valid email address"); + return false; +} + +return true; + +}; + +const handleCreateAccount = async () => { + if(!password || !confirmPassword){ + toast.error("Password and Confirm password are required."); + return; + } + + if (password !== confirmPassword){ + toast.error("Password do not match"); + return; + } + +const {address1, address2, address3, ...otherData} = formData; +const mergingAddress = [address1, address2, address3].filter(Boolean); +const fullAddress = mergingAddress.join(", "); + +const submissionData = { + ...otherData, + address: fullAddress, + password: password, +}; + +try { + const response = await fetch("api/auth/signup",{ + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(submissionData), + + }); + const result = await response.json(); + if(response.ok){ + toast.success("Signup successful!"); + setPasswordDialog(false); + setPassword(""); + setConfirmPassword(""); + } else { + toast.error(`Signup failed: ${result.message || "Unknown error"}`); + } +} catch (error) { + console.error("Error during signup:", error); + toast.error("An error occured during signup."); +} +}; + + @@ -66,39 +170,13 @@ const handleChange = (e) =>{ - //concat the address - const {address1, address2, address3, ...otherData} = formData; - const mergingAddress = [address1, address2, address3].filter(Boolean); - const fullAddress = mergingAddress.join(", "); - - const submissionData = { - ...otherData, - address: fullAddress, - } - - - - try { - const response = await fetch("api/auth/signup",{ - method: "POST", - headers:{ - "Content-Type": "application/json", - }, - body: JSON.stringify(submissionData), - - }); - const result = await response.json(); - - if(response.ok){ - toast.success("Signup succesfully"); - } else { - toast.error(`Signup failed: ${result.message || "Unknown error"}`); - } - } catch (error) { - console.error("Error during signup:", error); - toast.error("An error occurred during signup."); + if(validateMainFormData()){ + setPasswordDialog(true); - } + } + + + }} @@ -156,7 +234,7 @@ const handleChange = (e) =>{ className="border-black border-1 shadow-sm text-gray-700 p-1 w-[16rem]" placeholder=" Middle Name" - value={formData.middleName} + value={formData.mname} onChange={handleChange} required /> @@ -167,7 +245,7 @@ const handleChange = (e) =>{ className="border-black border-1 shadow-sm text-gray-700 p-1 w-[16rem]" placeholder=" Last Name" - value={formData.lastName} + value={formData.lname} onChange={handleChange} required /> @@ -208,7 +286,7 @@ const handleChange = (e) =>{ onChange={handleChange} required > - + @@ -229,6 +307,7 @@ const handleChange = (e) =>{ className=" text-gray-700 bg-white border border-black shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value={formData.role} + onChange={handleChange} > @@ -257,17 +336,7 @@ const handleChange = (e) =>{ required /> - {/* */} + @@ -365,6 +434,53 @@ const handleChange = (e) =>{ Rights Reserved

+ + {/* PasswordDialog */} + {showPasswordDialog && + ( +
+
+

+ Create Your Password +

+
+ + +
+
+ +
+ + + +
+ +
+ )} ); } From bf5cc03d7972d8bdd21b9f0a3c12e15e3bd1ca36 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 20 May 2025 16:34:13 +0800 Subject: [PATCH 03/18] add modification in signup --- backend/src/middlewares/auth.middleware.js | 2 +- frontend/src/pages/Signup.jsx | 63 +++++++++++++--------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/backend/src/middlewares/auth.middleware.js b/backend/src/middlewares/auth.middleware.js index 320e9be..9d88c3c 100644 --- a/backend/src/middlewares/auth.middleware.js +++ b/backend/src/middlewares/auth.middleware.js @@ -24,4 +24,4 @@ export const protectRoute = async(req, res, next) =>{ } catch (error) { } -} \ No newline at end of file +} diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 5799c96..9939905 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -150,13 +150,13 @@ try {
Hero
-
+
-
+

Fill out the form carefully for registration

@@ -218,8 +218,8 @@ try {
@@ -320,7 +320,7 @@ try { @@ -444,7 +444,7 @@ try { Create Your Password
-
+ +
+ +
-
+
+
-
)} From 424b0ecc734b03ca883c841d998ecd52a4218ed0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 24 May 2025 16:33:33 +0800 Subject: [PATCH 04/18] still coding the signup --- backend/node_modules/.package-lock.json | 22 ++ backend/node_modules/cors/CONTRIBUTING.md | 33 +++ backend/node_modules/cors/HISTORY.md | 58 +++++ backend/node_modules/cors/LICENSE | 22 ++ backend/node_modules/cors/README.md | 243 ++++++++++++++++++ backend/node_modules/cors/lib/index.js | 238 +++++++++++++++++ backend/node_modules/cors/package.json | 41 +++ backend/node_modules/object-assign/index.js | 90 +++++++ backend/node_modules/object-assign/license | 21 ++ .../node_modules/object-assign/package.json | 42 +++ backend/node_modules/object-assign/readme.md | 61 +++++ backend/package-lock.json | 23 ++ backend/package.json | 1 + backend/src/routes/auth.route.js | 6 + backend/src/server.js | 56 ++-- frontend/src/pages/Signup.jsx | 141 +++++----- 16 files changed, 1010 insertions(+), 88 deletions(-) create mode 100644 backend/node_modules/cors/CONTRIBUTING.md create mode 100644 backend/node_modules/cors/HISTORY.md create mode 100644 backend/node_modules/cors/LICENSE create mode 100644 backend/node_modules/cors/README.md create mode 100644 backend/node_modules/cors/lib/index.js create mode 100644 backend/node_modules/cors/package.json create mode 100644 backend/node_modules/object-assign/index.js create mode 100644 backend/node_modules/object-assign/license create mode 100644 backend/node_modules/object-assign/package.json create mode 100644 backend/node_modules/object-assign/readme.md diff --git a/backend/node_modules/.package-lock.json b/backend/node_modules/.package-lock.json index 8e347b4..b1a67e2 100644 --- a/backend/node_modules/.package-lock.json +++ b/backend/node_modules/.package-lock.json @@ -487,6 +487,19 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1634,6 +1647,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/backend/node_modules/cors/CONTRIBUTING.md b/backend/node_modules/cors/CONTRIBUTING.md new file mode 100644 index 0000000..591b09a --- /dev/null +++ b/backend/node_modules/cors/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# contributing to `cors` + +CORS is a node.js package for providing a [connect](http://www.senchalabs.org/connect/)/[express](http://expressjs.com/) middleware that can be used to enable [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) with various options. Learn more about the project in [the README](README.md). + +## The CORS Spec + +[http://www.w3.org/TR/cors/](http://www.w3.org/TR/cors/) + +## Pull Requests Welcome + +* Include `'use strict';` in every javascript file. +* 2 space indentation. +* Please run the testing steps below before submitting. + +## Testing + +```bash +$ npm install +$ npm test +``` + +## Interactive Testing Harness + +[http://node-cors-client.herokuapp.com](http://node-cors-client.herokuapp.com) + +Related git repositories: + +* [https://github.com/TroyGoode/node-cors-server](https://github.com/TroyGoode/node-cors-server) +* [https://github.com/TroyGoode/node-cors-client](https://github.com/TroyGoode/node-cors-client) + +## License + +[MIT License](http://www.opensource.org/licenses/mit-license.php) diff --git a/backend/node_modules/cors/HISTORY.md b/backend/node_modules/cors/HISTORY.md new file mode 100644 index 0000000..5762bce --- /dev/null +++ b/backend/node_modules/cors/HISTORY.md @@ -0,0 +1,58 @@ +2.8.5 / 2018-11-04 +================== + + * Fix setting `maxAge` option to `0` + +2.8.4 / 2017-07-12 +================== + + * Work-around Safari bug in default pre-flight response + +2.8.3 / 2017-03-29 +================== + + * Fix error when options delegate missing `methods` option + +2.8.2 / 2017-03-28 +================== + + * Fix error when frozen options are passed + * Send "Vary: Origin" when using regular expressions + * Send "Vary: Access-Control-Request-Headers" when dynamic `allowedHeaders` + +2.8.1 / 2016-09-08 +================== + +This release only changed documentation. + +2.8.0 / 2016-08-23 +================== + + * Add `optionsSuccessStatus` option + +2.7.2 / 2016-08-23 +================== + + * Fix error when Node.js running in strict mode + +2.7.1 / 2015-05-28 +================== + + * Move module into expressjs organization + +2.7.0 / 2015-05-28 +================== + + * Allow array of matching condition as `origin` option + * Allow regular expression as `origin` option + +2.6.1 / 2015-05-28 +================== + + * Update `license` in package.json + +2.6.0 / 2015-04-27 +================== + + * Add `preflightContinue` option + * Fix "Vary: Origin" header added for "*" diff --git a/backend/node_modules/cors/LICENSE b/backend/node_modules/cors/LICENSE new file mode 100644 index 0000000..fd10c84 --- /dev/null +++ b/backend/node_modules/cors/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2013 Troy Goode + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/backend/node_modules/cors/README.md b/backend/node_modules/cors/README.md new file mode 100644 index 0000000..732b847 --- /dev/null +++ b/backend/node_modules/cors/README.md @@ -0,0 +1,243 @@ +# cors + +[![NPM Version][npm-image]][npm-url] +[![NPM Downloads][downloads-image]][downloads-url] +[![Build Status][travis-image]][travis-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +CORS is a node.js package for providing a [Connect](http://www.senchalabs.org/connect/)/[Express](http://expressjs.com/) middleware that can be used to enable [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) with various options. + +**[Follow me (@troygoode) on Twitter!](https://twitter.com/intent/user?screen_name=troygoode)** + +* [Installation](#installation) +* [Usage](#usage) + * [Simple Usage](#simple-usage-enable-all-cors-requests) + * [Enable CORS for a Single Route](#enable-cors-for-a-single-route) + * [Configuring CORS](#configuring-cors) + * [Configuring CORS Asynchronously](#configuring-cors-asynchronously) + * [Enabling CORS Pre-Flight](#enabling-cors-pre-flight) +* [Configuration Options](#configuration-options) +* [Demo](#demo) +* [License](#license) +* [Author](#author) + +## Installation + +This is a [Node.js](https://nodejs.org/en/) module available through the +[npm registry](https://www.npmjs.com/). Installation is done using the +[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): + +```sh +$ npm install cors +``` + +## Usage + +### Simple Usage (Enable *All* CORS Requests) + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +app.use(cors()) + +app.get('/products/:id', function (req, res, next) { + res.json({msg: 'This is CORS-enabled for all origins!'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +### Enable CORS for a Single Route + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +app.get('/products/:id', cors(), function (req, res, next) { + res.json({msg: 'This is CORS-enabled for a Single Route'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +### Configuring CORS + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +var corsOptions = { + origin: 'http://example.com', + optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 +} + +app.get('/products/:id', cors(corsOptions), function (req, res, next) { + res.json({msg: 'This is CORS-enabled for only example.com.'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +### Configuring CORS w/ Dynamic Origin + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +var whitelist = ['http://example1.com', 'http://example2.com'] +var corsOptions = { + origin: function (origin, callback) { + if (whitelist.indexOf(origin) !== -1) { + callback(null, true) + } else { + callback(new Error('Not allowed by CORS')) + } + } +} + +app.get('/products/:id', cors(corsOptions), function (req, res, next) { + res.json({msg: 'This is CORS-enabled for a whitelisted domain.'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +If you do not want to block REST tools or server-to-server requests, +add a `!origin` check in the origin function like so: + +```javascript +var corsOptions = { + origin: function (origin, callback) { + if (whitelist.indexOf(origin) !== -1 || !origin) { + callback(null, true) + } else { + callback(new Error('Not allowed by CORS')) + } + } +} +``` + +### Enabling CORS Pre-Flight + +Certain CORS requests are considered 'complex' and require an initial +`OPTIONS` request (called the "pre-flight request"). An example of a +'complex' CORS request is one that uses an HTTP verb other than +GET/HEAD/POST (such as DELETE) or that uses custom headers. To enable +pre-flighting, you must add a new OPTIONS handler for the route you want +to support: + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +app.options('/products/:id', cors()) // enable pre-flight request for DELETE request +app.del('/products/:id', cors(), function (req, res, next) { + res.json({msg: 'This is CORS-enabled for all origins!'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +You can also enable pre-flight across-the-board like so: + +```javascript +app.options('*', cors()) // include before other routes +``` + +### Configuring CORS Asynchronously + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +var whitelist = ['http://example1.com', 'http://example2.com'] +var corsOptionsDelegate = function (req, callback) { + var corsOptions; + if (whitelist.indexOf(req.header('Origin')) !== -1) { + corsOptions = { origin: true } // reflect (enable) the requested origin in the CORS response + } else { + corsOptions = { origin: false } // disable CORS for this request + } + callback(null, corsOptions) // callback expects two parameters: error and options +} + +app.get('/products/:id', cors(corsOptionsDelegate), function (req, res, next) { + res.json({msg: 'This is CORS-enabled for a whitelisted domain.'}) +}) + +app.listen(80, function () { + console.log('CORS-enabled web server listening on port 80') +}) +``` + +## Configuration Options + +* `origin`: Configures the **Access-Control-Allow-Origin** CORS header. Possible values: + - `Boolean` - set `origin` to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS. + - `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed. + - `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com". + - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com". + - `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (which expects the signature `err [object], allow [bool]`) as the second. +* `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`). +* `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. +* `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. +* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. +* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. +* `preflightContinue`: Pass the CORS preflight response to the next handler. +* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`. + +The default configuration is the equivalent of: + +```json +{ + "origin": "*", + "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", + "preflightContinue": false, + "optionsSuccessStatus": 204 +} +``` + +For details on the effect of each CORS header, read [this](http://www.html5rocks.com/en/tutorials/cors/) article on HTML5 Rocks. + +## Demo + +A demo that illustrates CORS working (and not working) using jQuery is available here: [http://node-cors-client.herokuapp.com/](http://node-cors-client.herokuapp.com/) + +Code for that demo can be found here: + +* Client: [https://github.com/TroyGoode/node-cors-client](https://github.com/TroyGoode/node-cors-client) +* Server: [https://github.com/TroyGoode/node-cors-server](https://github.com/TroyGoode/node-cors-server) + +## License + +[MIT License](http://www.opensource.org/licenses/mit-license.php) + +## Author + +[Troy Goode](https://github.com/TroyGoode) ([troygoode@gmail.com](mailto:troygoode@gmail.com)) + +[coveralls-image]: https://img.shields.io/coveralls/expressjs/cors/master.svg +[coveralls-url]: https://coveralls.io/r/expressjs/cors?branch=master +[downloads-image]: https://img.shields.io/npm/dm/cors.svg +[downloads-url]: https://npmjs.org/package/cors +[npm-image]: https://img.shields.io/npm/v/cors.svg +[npm-url]: https://npmjs.org/package/cors +[travis-image]: https://img.shields.io/travis/expressjs/cors/master.svg +[travis-url]: https://travis-ci.org/expressjs/cors diff --git a/backend/node_modules/cors/lib/index.js b/backend/node_modules/cors/lib/index.js new file mode 100644 index 0000000..5475aec --- /dev/null +++ b/backend/node_modules/cors/lib/index.js @@ -0,0 +1,238 @@ +(function () { + + 'use strict'; + + var assign = require('object-assign'); + var vary = require('vary'); + + var defaults = { + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + preflightContinue: false, + optionsSuccessStatus: 204 + }; + + function isString(s) { + return typeof s === 'string' || s instanceof String; + } + + function isOriginAllowed(origin, allowedOrigin) { + if (Array.isArray(allowedOrigin)) { + for (var i = 0; i < allowedOrigin.length; ++i) { + if (isOriginAllowed(origin, allowedOrigin[i])) { + return true; + } + } + return false; + } else if (isString(allowedOrigin)) { + return origin === allowedOrigin; + } else if (allowedOrigin instanceof RegExp) { + return allowedOrigin.test(origin); + } else { + return !!allowedOrigin; + } + } + + function configureOrigin(options, req) { + var requestOrigin = req.headers.origin, + headers = [], + isAllowed; + + if (!options.origin || options.origin === '*') { + // allow any origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: '*' + }]); + } else if (isString(options.origin)) { + // fixed origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: options.origin + }]); + headers.push([{ + key: 'Vary', + value: 'Origin' + }]); + } else { + isAllowed = isOriginAllowed(requestOrigin, options.origin); + // reflect origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: isAllowed ? requestOrigin : false + }]); + headers.push([{ + key: 'Vary', + value: 'Origin' + }]); + } + + return headers; + } + + function configureMethods(options) { + var methods = options.methods; + if (methods.join) { + methods = options.methods.join(','); // .methods is an array, so turn it into a string + } + return { + key: 'Access-Control-Allow-Methods', + value: methods + }; + } + + function configureCredentials(options) { + if (options.credentials === true) { + return { + key: 'Access-Control-Allow-Credentials', + value: 'true' + }; + } + return null; + } + + function configureAllowedHeaders(options, req) { + var allowedHeaders = options.allowedHeaders || options.headers; + var headers = []; + + if (!allowedHeaders) { + allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers + headers.push([{ + key: 'Vary', + value: 'Access-Control-Request-Headers' + }]); + } else if (allowedHeaders.join) { + allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string + } + if (allowedHeaders && allowedHeaders.length) { + headers.push([{ + key: 'Access-Control-Allow-Headers', + value: allowedHeaders + }]); + } + + return headers; + } + + function configureExposedHeaders(options) { + var headers = options.exposedHeaders; + if (!headers) { + return null; + } else if (headers.join) { + headers = headers.join(','); // .headers is an array, so turn it into a string + } + if (headers && headers.length) { + return { + key: 'Access-Control-Expose-Headers', + value: headers + }; + } + return null; + } + + function configureMaxAge(options) { + var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString() + if (maxAge && maxAge.length) { + return { + key: 'Access-Control-Max-Age', + value: maxAge + }; + } + return null; + } + + function applyHeaders(headers, res) { + for (var i = 0, n = headers.length; i < n; i++) { + var header = headers[i]; + if (header) { + if (Array.isArray(header)) { + applyHeaders(header, res); + } else if (header.key === 'Vary' && header.value) { + vary(res, header.value); + } else if (header.value) { + res.setHeader(header.key, header.value); + } + } + } + } + + function cors(options, req, res, next) { + var headers = [], + method = req.method && req.method.toUpperCase && req.method.toUpperCase(); + + if (method === 'OPTIONS') { + // preflight + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options, req)); + headers.push(configureMethods(options, req)); + headers.push(configureAllowedHeaders(options, req)); + headers.push(configureMaxAge(options, req)); + headers.push(configureExposedHeaders(options, req)); + applyHeaders(headers, res); + + if (options.preflightContinue) { + next(); + } else { + // Safari (and potentially other browsers) need content-length 0, + // for 204 or they just hang waiting for a body + res.statusCode = options.optionsSuccessStatus; + res.setHeader('Content-Length', '0'); + res.end(); + } + } else { + // actual response + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options, req)); + headers.push(configureExposedHeaders(options, req)); + applyHeaders(headers, res); + next(); + } + } + + function middlewareWrapper(o) { + // if options are static (either via defaults or custom options passed in), wrap in a function + var optionsCallback = null; + if (typeof o === 'function') { + optionsCallback = o; + } else { + optionsCallback = function (req, cb) { + cb(null, o); + }; + } + + return function corsMiddleware(req, res, next) { + optionsCallback(req, function (err, options) { + if (err) { + next(err); + } else { + var corsOptions = assign({}, defaults, options); + var originCallback = null; + if (corsOptions.origin && typeof corsOptions.origin === 'function') { + originCallback = corsOptions.origin; + } else if (corsOptions.origin) { + originCallback = function (origin, cb) { + cb(null, corsOptions.origin); + }; + } + + if (originCallback) { + originCallback(req.headers.origin, function (err2, origin) { + if (err2 || !origin) { + next(err2); + } else { + corsOptions.origin = origin; + cors(corsOptions, req, res, next); + } + }); + } else { + next(); + } + } + }); + }; + } + + // can pass either an options hash, an options delegate, or nothing + module.exports = middlewareWrapper; + +}()); diff --git a/backend/node_modules/cors/package.json b/backend/node_modules/cors/package.json new file mode 100644 index 0000000..ff37d98 --- /dev/null +++ b/backend/node_modules/cors/package.json @@ -0,0 +1,41 @@ +{ + "name": "cors", + "description": "Node.js CORS middleware", + "version": "2.8.5", + "author": "Troy Goode (https://github.com/troygoode/)", + "license": "MIT", + "keywords": [ + "cors", + "express", + "connect", + "middleware" + ], + "repository": "expressjs/cors", + "main": "./lib/index.js", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "devDependencies": { + "after": "0.8.2", + "eslint": "2.13.1", + "express": "4.16.3", + "mocha": "5.2.0", + "nyc": "13.1.0", + "supertest": "3.3.0" + }, + "files": [ + "lib/index.js", + "CONTRIBUTING.md", + "HISTORY.md", + "LICENSE", + "README.md" + ], + "engines": { + "node": ">= 0.10" + }, + "scripts": { + "test": "npm run lint && nyc --reporter=html --reporter=text mocha --require test/support/env", + "lint": "eslint lib test" + } +} diff --git a/backend/node_modules/object-assign/index.js b/backend/node_modules/object-assign/index.js new file mode 100644 index 0000000..0930cf8 --- /dev/null +++ b/backend/node_modules/object-assign/index.js @@ -0,0 +1,90 @@ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +'use strict'; +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== + 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +module.exports = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; +}; diff --git a/backend/node_modules/object-assign/license b/backend/node_modules/object-assign/license new file mode 100644 index 0000000..654d0bf --- /dev/null +++ b/backend/node_modules/object-assign/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/backend/node_modules/object-assign/package.json b/backend/node_modules/object-assign/package.json new file mode 100644 index 0000000..503eb1e --- /dev/null +++ b/backend/node_modules/object-assign/package.json @@ -0,0 +1,42 @@ +{ + "name": "object-assign", + "version": "4.1.1", + "description": "ES2015 `Object.assign()` ponyfill", + "license": "MIT", + "repository": "sindresorhus/object-assign", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "xo && ava", + "bench": "matcha bench.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "object", + "assign", + "extend", + "properties", + "es2015", + "ecmascript", + "harmony", + "ponyfill", + "prollyfill", + "polyfill", + "shim", + "browser" + ], + "devDependencies": { + "ava": "^0.16.0", + "lodash": "^4.16.4", + "matcha": "^0.7.0", + "xo": "^0.16.0" + } +} diff --git a/backend/node_modules/object-assign/readme.md b/backend/node_modules/object-assign/readme.md new file mode 100644 index 0000000..1be09d3 --- /dev/null +++ b/backend/node_modules/object-assign/readme.md @@ -0,0 +1,61 @@ +# object-assign [![Build Status](https://travis-ci.org/sindresorhus/object-assign.svg?branch=master)](https://travis-ci.org/sindresorhus/object-assign) + +> ES2015 [`Object.assign()`](http://www.2ality.com/2014/01/object-assign.html) [ponyfill](https://ponyfill.com) + + +## Use the built-in + +Node.js 4 and up, as well as every evergreen browser (Chrome, Edge, Firefox, Opera, Safari), +support `Object.assign()` :tada:. If you target only those environments, then by all +means, use `Object.assign()` instead of this package. + + +## Install + +``` +$ npm install --save object-assign +``` + + +## Usage + +```js +const objectAssign = require('object-assign'); + +objectAssign({foo: 0}, {bar: 1}); +//=> {foo: 0, bar: 1} + +// multiple sources +objectAssign({foo: 0}, {bar: 1}, {baz: 2}); +//=> {foo: 0, bar: 1, baz: 2} + +// overwrites equal keys +objectAssign({foo: 0}, {foo: 1}, {foo: 2}); +//=> {foo: 2} + +// ignores null and undefined sources +objectAssign({foo: 0}, null, {bar: 1}, undefined); +//=> {foo: 0, bar: 1} +``` + + +## API + +### objectAssign(target, [source, ...]) + +Assigns enumerable own properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones. + + +## Resources + +- [ES2015 spec - Object.assign](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign) + + +## Related + +- [deep-assign](https://github.com/sindresorhus/deep-assign) - Recursive `Object.assign()` + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/backend/package-lock.json b/backend/package-lock.json index c14fe1e..7044a48 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^3.0.2", + "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", @@ -506,6 +507,19 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1668,6 +1682,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/backend/package.json b/backend/package.json index 4f3cb9e..a2e12bf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,7 @@ { "dependencies": { "bcryptjs": "^3.0.2", + "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", diff --git a/backend/src/routes/auth.route.js b/backend/src/routes/auth.route.js index 40ff4f2..51586f9 100644 --- a/backend/src/routes/auth.route.js +++ b/backend/src/routes/auth.route.js @@ -1,5 +1,6 @@ import express from 'express'; import { signup, login, logout } from '../controllers/auth.controller.js'; +// import {protectRoute} from '../middlewares/auth.middleware.js'; const router = express.Router(); @@ -8,4 +9,9 @@ router.post("/signup",signup); router.post("/logout",logout); + +//middlewares +// router.post("/update-profile", protectRoute, updateProfile); +// router.post("/update-password", protectRoute, updatePassword); + export default router; \ No newline at end of file diff --git a/backend/src/server.js b/backend/src/server.js index b32aae8..5f81653 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -1,27 +1,33 @@ -import express, {Router} from 'express'; +import express from 'express'; import dotenv from 'dotenv' import bodyparser from 'body-parser'; import sequelize from './utils/db.js'; import EmployeeModel from './models/employee.model.js'; import CrudRoutes from './routes/crud.route.js' import authRoute from './routes/auth.route.js' +import cors from 'cors' const app = express(); +const corsOptions = { + origin: 'http://localhost:5173', + methods: ['GET','POST','PUT','DELETE','OPTIONS'], + allowedHeaders:['Content-Type', 'Authorization'], + optionsSuccessStatus: 200 +}; + dotenv.config(); const PORT = process.env.PORT || 5001; -//middleware -app.use(bodyparser.json()); +//middlewares +app.use(express.json()); app.use(bodyparser.urlencoded({extended: false})); -app.use((req, res, next) =>{ - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); - next(); +app.use(cors(corsOptions)); + + -}); //test routes CRUD @@ -33,12 +39,28 @@ app.get('/',(req,res,next)=>{ app.use('/employees',CrudRoutes); app.use('/api/auth', authRoute); //error handling -app.use((error, req,res,next) =>{ - console.log(error); +app.use((error, req, res, next) =>{ // Added 'next' for completeness, though not strictly used here + console.error("--- GLOBAL ERROR HANDLER CAUGHT ---"); // More prominent logging + console.error("Timestamp:", new Date().toISOString()); + console.error("Request URL:", req.originalUrl); + console.error("Request Method:", req.method); + console.error("Error Name:", error.name); + console.error("Error Message:", error.message); + console.error("Error Status Code:", error.statusCode); + if (error.data) { + console.error("Error Data:", error.data); + } + console.error("Error Stack:", error.stack); // Log the full stack + console.error("-----------------------------------"); + const status = error.statusCode || 500; - const message = error.message; - const data = error.data - res.status(status).json({message: message}); + + const message = error.message || "An internal server error occurred."; + const responseError = { message: message }; + if (error.data) { // If the error object has a 'data' property, include it + responseError.data = error.data; + } + res.status(status).json(responseError); }); @@ -64,12 +86,4 @@ startServer(); -// app.use(express.json()); -// app.use("/api/auth", authRoute); - - - -// app.listen(5001,() =>{ -// console.log(`Server is running on PORT ${PORT}`); -// }) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 9939905..38c7d27 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -3,9 +3,9 @@ import Navbar from "../components/Navbar.jsx"; import {Toaster, toast} from "react-hot-toast"; function Signup() { - const [formData, setFormData] = useState({ +const initialFormData = { //name - emp_id: "", + fname: "", mname: "", lname: "", @@ -29,9 +29,12 @@ function Signup() { profileImage: null, - +} - }); + + + + const [formData, setFormData] = useState(initialFormData); //password const [showPasswordDialog, setPasswordDialog] = useState(false); const [password, setPassword] = useState(""); @@ -47,44 +50,28 @@ const handlePasswordChange = (e) => setPassword(e.target.value); const handleConfirmPasswordChange = (e) => setConfirmPassword(e.target.value); const validateMainFormData = () =>{ - const requiredFields = { - - fname: true, - mname: true, - lname: true, - birthdate: true, - sex: true, - position: true, - role: true, - address1: true, - email: true, - }; - - //may babaguhin pa const fieldDisplayNames = { - fname: "First Name", mname: "Middle Name", lname: "Last Name", birthdate: "Birthday", sex: "Sex", position: "Position", - role: "Role", + role: "Acces Level", address1: "Address Line 1", email: "Email", + phoneNumber: "Phone Number", }; - - - -for (const field in requiredFields){ - if(requiredFields[field] && !formData[field]){ +for (const field in fieldDisplayNames){ + if(Object.prototype.hasOwnProperty.call(formData, field) && !formData[field]){ toast.error(`${fieldDisplayNames[field]} is required.`); return false; } + } if(formData.email && !/\S+@\S+\.\S+/.test(formData.email)){ toast.error("Please enter a valid email address"); @@ -117,27 +104,60 @@ const submissionData = { }; try { - const response = await fetch("api/auth/signup",{ + const response = await fetch("http://localhost:5001/api/auth/signup",{ method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(submissionData), + }); - const result = await response.json(); - if(response.ok){ - toast.success("Signup successful!"); - setPasswordDialog(false); + + + let result; + + try { + result = await response.json(); + + } catch (jsonError) { + console.error("Error parsing JSON from response:",jsonError, "Status: ", response.status, "StatusText: ", response.statusText) + + + //new changes + + let textReponse = ""; + + try { + textReponse = await response.text(); + console.error ("Non-JSON response text:", textReponse); + } catch (textError) { + console.error("Could not get text from non-JSON response:", textError); + } + toast.error(`Signup failed: Server returned an unexpected response, Status: ${response.status}.${textReponse}`) + return; + } + + if (response.ok){ + toast.success(result.message || "Signup successful"); + + //reset form and password fields on success + setFormData(initialFormData); setPassword(""); setConfirmPassword(""); - } else { - toast.error(`Signup failed: ${result.message || "Unknown error"}`); + setPasswordDialog(false); + + } else{ + console.error("Signup failed with status:", response.status, "Backend response:", result); + toast.error(`Signup failed: ${result.message || "An unknown error occurred on the server"}`); } -} catch (error) { - console.error("Error during signup:", error); - toast.error("An error occured during signup."); -} + +} catch (networkError) { + console.error("Network error during signup:", networkError); + toast.error("A network error occurred. Please check your connection or if the server is running." ) + + }; +}; @@ -156,7 +176,7 @@ try {
-
+

Fill out the form carefully for registration

@@ -194,22 +214,7 @@ try { EDIT PROFILE
- {/* empid */} - - +
{/* Employee name */}
- + {/* button */}
- + -
)} + ); } From 0a5b1f310b8b0ce7ba2bb9d63b147c34cc087f29 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 24 May 2025 16:35:57 +0800 Subject: [PATCH 05/18] edit handleCreateaccount --- frontend/src/pages/Signup.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 38c7d27..4840e7b 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -85,6 +85,7 @@ return true; const handleCreateAccount = async () => { if(!password || !confirmPassword){ toast.error("Password and Confirm password are required."); + console.log("Submitting data to backend:", JSON.stringify(submissionData, null, 2)); return; } From c0b42bc11db4d9461b4117cd8247e1d96f8f2482 Mon Sep 17 00:00:00 2001 From: snowhyt Date: Mon, 26 May 2025 02:40:38 +0800 Subject: [PATCH 06/18] successfully added a record in signup form, remove profile pic --- backend/src/controllers/auth.controller.js | 39 ++++++++------ backend/src/models/employee.model.js | 2 +- frontend/src/pages/Signup.jsx | 63 +++++++++++----------- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index ecd121f..f0b52bb 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -5,20 +5,21 @@ import { generateToken } from "../utils/utils.js"; //SIGNUP export const signup = async(req,res,next) =>{ + console.log("Signup request received"); const {fname,lname,mname,sex,birthdate,email,password,role,position,profilePic,phone, address} = req.body; try { //check if all fields have data - if(!fname || !lname || !mname || !sex || !birthdate || !email || !password || !role || !position || !profilePic || !phone || !address) + if(!fname || !lname || !sex || !birthdate || !email || !password || !role || !position || !phone || !address) { - const error = new Error("All fields are required"); + const error = new Error("All fields are required"); error.statusCode = 400; return next(error); } - //accepts admin and employee only (accesibility) + //accepts admin and employee only (accessibility) - if (role !== 'admin' && role !== 'employee'){ + if (role !== 'admin' && role !== 'staff'){ return res.status(400).json({message: "Invalid role. Choose the right role"}); } @@ -50,21 +51,25 @@ export const signup = async(req,res,next) =>{ const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); + // https://avatar-placeholder.iran.liara.run/ + const boyProfilePic = `https://avatar.iran.liara.run/public/boy?username=${email}`; + const girlProfilePic = `https://avatar.iran.liara.run/public/girl?username=${email}`; //for new employee + const newEmployee = await Employee.create({ - fname, - lname, - mname, - sex: sex.toLowerCase(), - birthdate, - email: email.toLowerCase(), - password: hashedPassword, - role, - position, - profilePic, - phone, - address + fname, + lname, + mname, + sex: sex.toLowerCase(), + birthdate, + email: email.toLowerCase(), + password: hashedPassword, + role, + position, + phone, + address, + profilePic : sex === "male" ? boyProfilePic : girlProfilePic }); //generate token @@ -81,6 +86,7 @@ export const signup = async(req,res,next) =>{ id: newEmployee.id, fname: newEmployee.fname, lname: newEmployee.lname, + mname: newEmployee.mname, sex: newEmployee.sex, birthdate: newEmployee.birthdate, email: newEmployee.email, @@ -145,7 +151,6 @@ export const login = async(req,res) =>{ email: LoginEmployee.email, role: LoginEmployee.role, position: LoginEmployee.position, - profilePic: LoginEmployee.profilePic, phone: LoginEmployee.phone, address: LoginEmployee.address } diff --git a/backend/src/models/employee.model.js b/backend/src/models/employee.model.js index 4affb73..c265c24 100644 --- a/backend/src/models/employee.model.js +++ b/backend/src/models/employee.model.js @@ -26,7 +26,7 @@ const Employee = db.define('employee',{ }, mname:{ type: DataTypes.STRING, - allowNull: false, + allowNull: true, }, sex:{ type: DataTypes.ENUM('male', 'female'), diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 4840e7b..add1cb4 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -4,18 +4,14 @@ import {Toaster, toast} from "react-hot-toast"; function Signup() { const initialFormData = { - //name - fname: "", mname: "", lname: "", - - //birthdate and job role birthdate: "", sex: "", position: "", role: "", - + // Address address1: "", address2: "", @@ -23,10 +19,9 @@ const initialFormData = { // Contact email: "", - phoneNumber: "", + phone: "", - //picture - profileImage: null, + } @@ -50,7 +45,7 @@ const handlePasswordChange = (e) => setPassword(e.target.value); const handleConfirmPasswordChange = (e) => setConfirmPassword(e.target.value); const validateMainFormData = () =>{ - +//for validation only const fieldDisplayNames = { fname: "First Name", mname: "Middle Name", @@ -58,10 +53,11 @@ const validateMainFormData = () =>{ birthdate: "Birthday", sex: "Sex", position: "Position", - role: "Acces Level", + role: "Access Level", address1: "Address Line 1", email: "Email", - phoneNumber: "Phone Number", + phone: "Phone", + address: "Address", }; @@ -125,15 +121,15 @@ try { //new changes - let textReponse = ""; + let textResponse = ""; try { - textReponse = await response.text(); - console.error ("Non-JSON response text:", textReponse); + textResponse = await response.text(); + console.error ("Non-JSON response text:", textResponse); } catch (textError) { console.error("Could not get text from non-JSON response:", textError); } - toast.error(`Signup failed: Server returned an unexpected response, Status: ${response.status}.${textReponse}`) + toast.error(`Signup failed: Server returned an unexpected response, Status: ${response.status}.${textResponse}`) return; } @@ -177,7 +173,7 @@ try {
-
+

Fill out the form carefully for registration

@@ -202,9 +198,9 @@ try { }} > -
+ {/*
- {/* profile */} + Profile EDIT PROFILE -
- +
*/} +
{/* Employee name */}
@@ -445,13 +441,13 @@ try { {/* PasswordDialog */} {showPasswordDialog && ( -
+

Create Your Password

-
@@ -481,25 +477,26 @@ try { className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" />
-
+ {/* button */} +
- +
)} From 87f235694b0017d1161763180d97365c717a268b Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 26 May 2025 12:00:14 +0800 Subject: [PATCH 07/18] add created successfully dialog or modal --- backend/src/controllers/auth.controller.js | 8 ++- frontend/src/pages/Signup.jsx | 74 ++++++++++++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index f0b52bb..df1ff40 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -69,7 +69,7 @@ export const signup = async(req,res,next) =>{ position, phone, address, - profilePic : sex === "male" ? boyProfilePic : girlProfilePic + profilePic : sex.toLowerCase() === "male" ? boyProfilePic : girlProfilePic }); //generate token @@ -92,6 +92,9 @@ export const signup = async(req,res,next) =>{ email: newEmployee.email, role: newEmployee.role, position: newEmployee.position, + profilePic: newEmployee.profilePic, + phone: newEmployee.phone, + address: newEmployee.address } }) @@ -152,7 +155,8 @@ export const login = async(req,res) =>{ role: LoginEmployee.role, position: LoginEmployee.position, phone: LoginEmployee.phone, - address: LoginEmployee.address + address: LoginEmployee.address, + profilePic: LoginEmployee.profilePic } }) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index add1cb4..f87c818 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,6 +1,7 @@ import React, {useState} from "react"; import Navbar from "../components/Navbar.jsx"; import {Toaster, toast} from "react-hot-toast"; +import {useNavigate} from "react-router-dom"; function Signup() { const initialFormData = { @@ -35,6 +36,10 @@ const initialFormData = { const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); + //account created modal + const [showAccountCreatedDialog, setShowAccountCreatedDialog] = useState(false); + const [newAccountDetails, setNewAccountDetails] = useState(null); + const navigate = useNavigate(); const handleChange = (e) =>{ const {name, value} = e.target; @@ -81,7 +86,6 @@ return true; const handleCreateAccount = async () => { if(!password || !confirmPassword){ toast.error("Password and Confirm password are required."); - console.log("Submitting data to backend:", JSON.stringify(submissionData, null, 2)); return; } @@ -137,9 +141,8 @@ try { toast.success(result.message || "Signup successful"); //reset form and password fields on success - setFormData(initialFormData); - setPassword(""); - setConfirmPassword(""); + setNewAccountDetails(result.employee); + setShowAccountCreatedDialog(true); setPasswordDialog(false); } else{ @@ -156,6 +159,20 @@ try { }; +const handleGoToLogin = () =>{ + if(newAccountDetails && newAccountDetails.emp_id){ + navigate('/login', {state:{emp_id: newAccountDetails.emp_id}}); + } else{ + navigate('/login'); + } + setShowAccountCreatedDialog(false); + setNewAccountDetails(null); + //reset or clear form inputs + setFormData(initialFormData); + setPassword(""); + setConfirmPassword(""); +} + @@ -475,7 +492,8 @@ try { onChange={handleConfirmPasswordChange} className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" - /> + placeholder="Re-enter password" + />
{/* button */} @@ -500,6 +518,52 @@ try {
)} + + {/* Account Created Dialog/Modal */} + {showAccountCreatedDialog && newAccountDetails &&( +
+
+

+ Account Created Successfully! +

+

Please take note of your employee ID and password.

+
+ profilePic{ + //fallback oif URL fails to load + e.target.oneerror = null; + e.target.src = "images/profile.jpeg" + }} + /> + + {/* show emp_id */} +

+ Employee ID: {newAccountDetails.emp_id} +

+ {/* show fullname */} +

+ Full name: + {`${newAccountDetails.fname + " "}${newAccountDetails.mname ? newAccountDetails.mname + ' ' : ' '}${newAccountDetails.lname}`} +

+ {/* show trabaho */} +

+ Email: + {newAccountDetails.email} + +

+ +
+ +
+
+ )} ); From 96f7c8b45aafb4ddd0cccd6cf2e51b414d1f9877 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 26 May 2025 16:44:33 +0800 Subject: [PATCH 08/18] login setup 80% done --- backend/src/controllers/auth.controller.js | 2 + backend/src/routes/auth.route.js | 2 +- frontend/src/components/Footer.jsx | 29 ++++ frontend/src/pages/Login.jsx | 175 +++++++++++++++++---- frontend/src/pages/Signup.jsx | 39 ++++- 5 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/Footer.jsx diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index df1ff40..c6048fc 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -3,6 +3,7 @@ import express from 'express'; import bcrypt from 'bcryptjs'; import { generateToken } from "../utils/utils.js"; + //SIGNUP export const signup = async(req,res,next) =>{ console.log("Signup request received"); @@ -144,6 +145,7 @@ export const login = async(req,res) =>{ //send response res.status(200).json({ message: "Login successful", + token: LoginEmployee.token, employee:{ id: LoginEmployee.id, fname: LoginEmployee.fname, diff --git a/backend/src/routes/auth.route.js b/backend/src/routes/auth.route.js index 51586f9..db1561c 100644 --- a/backend/src/routes/auth.route.js +++ b/backend/src/routes/auth.route.js @@ -1,6 +1,6 @@ import express from 'express'; import { signup, login, logout } from '../controllers/auth.controller.js'; -// import {protectRoute} from '../middlewares/auth.middleware.js'; +import {protectRoute} from '../middlewares/auth.middleware.js'; const router = express.Router(); diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx new file mode 100644 index 0000000..8ecaf11 --- /dev/null +++ b/frontend/src/components/Footer.jsx @@ -0,0 +1,29 @@ +import React from "react"; + +function Footer() { + return ( + <> + {/* Footer */} +
+
+ Logo +
+
+ +

+ Copyright Metropolitan Pest Control Corporation. All + Rights Reserved +

+
+ + + + +) +} + +export default Footer; diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 8ac5e22..55ec6b8 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -1,41 +1,156 @@ -import React from "react"; +import React, {useState, useEffect} from "react"; import Navbar from "../components/Navbar.jsx"; +import {Toaster, toast} from "react-hot-toast"; +import { useNavigate, useLocation, Link } from "react-router-dom"; +import Footer from "../components/Footer.jsx"; + function Login() { + //initialize to object + const navigate = useNavigate(); + const location = useLocation(); + + + const initialFormData = { + emp_id: "", + password: "", + } + + const [formData, setFormData] = useState(initialFormData); + + //for loading screen + const [loading, setLoading] = useState(false); + + + + useEffect(() =>{ + + if(location.state?.emp_id){ + setFormData((prevData) =>({ + ...prevData, + emp_id: location.state.emp_id + + })); + navigate(location.pathname, { replace: true, state: {} }) + } + },[location.state]); + + const handleChange = (e) =>{ + const {name, value} = e.target; + setFormData((prevData) => ({...prevData, [name]: value})); + } + + + //HANDLE LOGIN FUNCTION + const handleLogin = async (e) => { + e.preventDefault(); + setLoading(true); + + //basic validation + if(!formData.emp_id){ + toast.error("Employee ID is required"); + setLoading(false); + return; + } + + if(!formData.password){ + toast.error("Password is required"); + setLoading(false); + return; + } + + try { + const response =await fetch("http://localhost:5001/api/auth/login",{ + method: "POST", + headers: {"Content-Type" : "application/json"}, + body: JSON.stringify(formData), + + }); + + let result; + try{ + result = await response.json(); + } catch(jsonError){ + console.error("Error parsng JSON from login response:", jsonError); + const textResponse = await response.text(); + toast.error(`Login failed: Server returned an unexpected response. Status: ${response.status}. ${textResponse}`); + setLoading(false); + return; + } + if(response.ok){ + toast.success(result.message|| "Login successful!"); + + //Store token and user data in LocalStorage + if(result.token){ + localStorage.setItem("authToken", result.token); + } + if(result.employee){ + localStorage.setItem("user",JSON.stringify(result.employee)); + } + + + + console.log("Logged in employee", result.employee); + + navigate("/dashboard"); + } else { + toast.error(result.message ||"Login Failed. Please check your credentials."); + + } + + } catch (networkError) { + console.error("Network error during login:", networkError); + toast.error("Network error. Please check your internet connection."); + + } finally{ + setLoading(false); + } + } + + + + + + + + return ( <> - + {/* Hero Image */}
Hero Section
{/* Login Section */}
-
+

LOGIN

-
+ {/* Username Field */}
@@ -53,51 +168,43 @@ function Login() { name="password" required placeholder="Enter your password" - className="w-full mt-1 px-4 py-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-700" - /> + className="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-700" + + value={formData.password} + onChange={handleChange} + />
{/* Submit Button */}
+

Don't have an account?{" "} - Sign up - +

- {/* Footer */} -
-
- Logo -
-
- -

- Copyright Metropolitan Pest Control Corporation. All - Rights Reserved -

-
+
); } diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index f87c818..7140c58 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,7 +1,8 @@ import React, {useState} from "react"; import Navbar from "../components/Navbar.jsx"; +import Footer from "../components/Footer.jsx"; import {Toaster, toast} from "react-hot-toast"; -import {useNavigate} from "react-router-dom"; +import {useNavigate, Link} from "react-router-dom"; function Signup() { const initialFormData = { @@ -190,11 +191,11 @@ const handleGoToLogin = () =>{
-
-

+
+

Fill out the form carefully for registration

-
+
{/* this is the form */}
{
@@ -436,15 +437,37 @@ const handleGoToLogin = () =>{
+ + {/* Don't have an account? */} +
+

+ Have an existing account?{" "} + + + Login + +

+

+ + +
+ + + + + {/* Footer */}
Logo
@@ -454,6 +477,8 @@ const handleGoToLogin = () =>{ Rights Reserved

+
+ {/* PasswordDialog */} {showPasswordDialog && @@ -515,8 +540,10 @@ const handleGoToLogin = () =>{ Create Account
+
+ )} {/* Account Created Dialog/Modal */} From 6c1b56661507190b9a2118bc24c980abcfc6676d Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 26 May 2025 16:46:08 +0800 Subject: [PATCH 09/18] additional changes in Login page --- frontend/src/pages/Login.jsx | 2 +- frontend/src/pages/Signup.jsx | 17 +---------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 55ec6b8..3383b4c 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -33,7 +33,7 @@ function Login() { })); navigate(location.pathname, { replace: true, state: {} }) } - },[location.state]); + },[location.state, navigate,location.pathname]); const handleChange = (e) =>{ const {name, value} = e.target; diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index 7140c58..c617f91 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -461,22 +461,7 @@ const handleGoToLogin = () =>{ - {/* Footer */} -
-
- Logo -
-
- -

- Copyright Metropolitan Pest Control Corporation. All - Rights Reserved -

-
+