├── .gitignore ├── README.md ├── backend ├── db │ ├── Application.js │ ├── Job.js │ ├── JobApplicant.js │ ├── Rating.js │ ├── Recruiter.js │ └── User.js ├── lib │ ├── authKeys.js │ ├── jwtAuth.js │ └── passportConfig.js ├── package-lock.json ├── package.json ├── routes │ ├── apiRoutes.js │ ├── authRoutes.js │ ├── downloadRoutes.js │ └── uploadRoutes.js └── server.js ├── dummyData ├── frontend ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── component │ ├── Applications.js │ ├── Home.js │ ├── Login.js │ ├── Logout.js │ ├── Navbar.js │ ├── Profile.js │ ├── Signup.js │ ├── Welcome.js │ └── recruiter │ │ ├── AcceptedApplicants.js │ │ ├── CreateJobs.js │ │ ├── JobApplications.js │ │ ├── MyJobs.js │ │ └── Profile.js │ ├── index.css │ ├── index.js │ ├── lib │ ├── EmailInput.js │ ├── FileUploadInput.js │ ├── MessagePopup.js │ ├── PasswordInput.js │ ├── apiList.js │ └── isAuth.js │ ├── reportWebVitals.js │ └── setupTests.js └── package-lock.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | © 2021 GitHub, Inc. 106 | Terms 107 | Privacy 108 | Security 109 | Status 110 | Help 111 | Contact GitHub 112 | Pricing 113 | API 114 | Training 115 | Blog 116 | About 117 | Octotree 118 | Login with GitHub 119 | 120 | backend/public/profile/* 121 | backend/public/resume/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Job Portal 2 | 3 | Job Portal is a MERN Stack based web app which helps in streamlining the flow of job application process. It allows users to select there roles (applicant/recruiter), and create an account. In this web app, login session are persistent and REST APIs are securely protected by JWT token verification. After logging in, a recruiter can create/delete/update jobs, shortlist/accept/reject applications, view resume and edit profile. And, an applicant can view jobs, perform fuzzy search with various filters, apply for jobs with an SOP, view applications, upload profile picture, upload resume and edit profile. Hence, it is an all in one solution for a job application system. 4 | 5 | Demo: [Click Here](https://www.youtube.com/watch?v=lIrN-LbbBnw&ab_channel=ShlokPandey) 6 | 7 | Directory structure of the web app is as follows: 8 | 9 | ``` 10 | - backend/ 11 | - public/ 12 | - profile/ 13 | - resume/ 14 | - frontend/ 15 | - README.md 16 | ``` 17 | 18 | ## Instructions for initializing web app: 19 | 20 | - Install Node JS, MongoDB in the machine. 21 | - Start MongoDB server: `sudo service mongod start` 22 | - Move inside backend directory: `cd backend` 23 | - Install dependencies in backend directory: `npm install` 24 | - Start express server: `npm start` 25 | - Backend server will start on port 4444. 26 | - Now go inside frontend directory: `cd ..\frontend` 27 | - Install dependencies in frontend directory: `npm install` 28 | - Start web app's frontend server: `npm start` 29 | - Frontend server will start on port 3000. 30 | - Now open `http://localhost:3000/` and proceed creating jobs and applications by signing up in required categories. 31 | 32 | ## Dependencies: 33 | 34 | - Frontend 35 | - @material-ui/core 36 | - @material-ui/icons 37 | - @material-ui/lab 38 | - axios 39 | - material-ui-chip-input 40 | - react-phone-input-2 41 | - Backend 42 | - bcrypt 43 | - body-parser 44 | - connect-flash 45 | - connect-mongo 46 | - cors 47 | - crypto 48 | - express 49 | - express-session 50 | - jsonwebtoken 51 | - mongoose 52 | - mongoose-type-email 53 | - multer 54 | - passport 55 | - passport-jwt 56 | - passport-local 57 | - uuid 58 | 59 | # Machine Specifications 60 | 61 | Details of the machine on which the webapp was tested: 62 | 63 | - Operating System: Elementary OS 5.1 (Hera) 64 | - Terminal: Bash 65 | - Processor: Intel Core i7-8750H CPU @ 2.20 GHz 2.21 GHz 66 | - RAM: 16 GB 67 | -------------------------------------------------------------------------------- /backend/db/Application.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | let schema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | }, 9 | recruiterId: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | required: true, 12 | }, 13 | jobId: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | required: true, 16 | }, 17 | status: { 18 | type: String, 19 | enum: [ 20 | "applied", // when a applicant is applied 21 | "shortlisted", // when a applicant is shortlisted 22 | "accepted", // when a applicant is accepted 23 | "rejected", // when a applicant is rejected 24 | "deleted", // when any job is deleted 25 | "cancelled", // an application is cancelled by its author or when other application is accepted 26 | "finished", // when job is over 27 | ], 28 | default: "applied", 29 | required: true, 30 | }, 31 | dateOfApplication: { 32 | type: Date, 33 | default: Date.now, 34 | }, 35 | dateOfJoining: { 36 | type: Date, 37 | validate: [ 38 | { 39 | validator: function (value) { 40 | return this.dateOfApplication <= value; 41 | }, 42 | msg: "dateOfJoining should be greater than dateOfApplication", 43 | }, 44 | ], 45 | }, 46 | sop: { 47 | type: String, 48 | validate: { 49 | validator: function (v) { 50 | return v.split(" ").filter((ele) => ele != "").length <= 250; 51 | }, 52 | msg: "Statement of purpose should not be greater than 250 words", 53 | }, 54 | }, 55 | }, 56 | { collation: { locale: "en" } } 57 | ); 58 | 59 | // schema.virtual("applicationUser", { 60 | // ref: "JobApplicantInfo", 61 | // localField: "userId", 62 | // foreignField: "userId", 63 | // justOne: true, 64 | // }); 65 | 66 | // schema.virtual("applicationRecruiter", { 67 | // ref: "RecruiterInfo", 68 | // localField: "recruiterId", 69 | // foreignField: "userId", 70 | // justOne: true, 71 | // }); 72 | 73 | // schema.virtual("applicationJob", { 74 | // ref: "jobs", 75 | // localField: "jobId", 76 | // foreignField: "_id", 77 | // justOne: true, 78 | // }); 79 | 80 | module.exports = mongoose.model("applications", schema); 81 | -------------------------------------------------------------------------------- /backend/db/Job.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | let schema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | }, 9 | title: { 10 | type: String, 11 | required: true, 12 | }, 13 | maxApplicants: { 14 | type: Number, 15 | validate: [ 16 | { 17 | validator: Number.isInteger, 18 | msg: "maxApplicants should be an integer", 19 | }, 20 | { 21 | validator: function (value) { 22 | return value > 0; 23 | }, 24 | msg: "maxApplicants should greater than 0", 25 | }, 26 | ], 27 | }, 28 | maxPositions: { 29 | type: Number, 30 | validate: [ 31 | { 32 | validator: Number.isInteger, 33 | msg: "maxPostions should be an integer", 34 | }, 35 | { 36 | validator: function (value) { 37 | return value > 0; 38 | }, 39 | msg: "maxPositions should greater than 0", 40 | }, 41 | ], 42 | }, 43 | activeApplications: { 44 | type: Number, 45 | default: 0, 46 | validate: [ 47 | { 48 | validator: Number.isInteger, 49 | msg: "activeApplications should be an integer", 50 | }, 51 | { 52 | validator: function (value) { 53 | return value >= 0; 54 | }, 55 | msg: "activeApplications should greater than equal to 0", 56 | }, 57 | ], 58 | }, 59 | acceptedCandidates: { 60 | type: Number, 61 | default: 0, 62 | validate: [ 63 | { 64 | validator: Number.isInteger, 65 | msg: "acceptedCandidates should be an integer", 66 | }, 67 | { 68 | validator: function (value) { 69 | return value >= 0; 70 | }, 71 | msg: "acceptedCandidates should greater than equal to 0", 72 | }, 73 | ], 74 | }, 75 | dateOfPosting: { 76 | type: Date, 77 | default: Date.now, 78 | }, 79 | deadline: { 80 | type: Date, 81 | validate: [ 82 | { 83 | validator: function (value) { 84 | return this.dateOfPosting < value; 85 | }, 86 | msg: "deadline should be greater than dateOfPosting", 87 | }, 88 | ], 89 | }, 90 | skillsets: [String], 91 | jobType: { 92 | type: String, 93 | required: true, 94 | }, 95 | duration: { 96 | type: Number, 97 | min: 0, 98 | validate: [ 99 | { 100 | validator: Number.isInteger, 101 | msg: "Duration should be an integer", 102 | }, 103 | ], 104 | }, 105 | salary: { 106 | type: Number, 107 | validate: [ 108 | { 109 | validator: Number.isInteger, 110 | msg: "Salary should be an integer", 111 | }, 112 | { 113 | validator: function (value) { 114 | return value >= 0; 115 | }, 116 | msg: "Salary should be positive", 117 | }, 118 | ], 119 | }, 120 | rating: { 121 | type: Number, 122 | max: 5.0, 123 | default: -1.0, 124 | validate: { 125 | validator: function (v) { 126 | return v >= -1.0 && v <= 5.0; 127 | }, 128 | msg: "Invalid rating", 129 | }, 130 | }, 131 | }, 132 | { collation: { locale: "en" } } 133 | ); 134 | 135 | module.exports = mongoose.model("jobs", schema); 136 | -------------------------------------------------------------------------------- /backend/db/JobApplicant.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | let schema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | }, 9 | name: { 10 | type: String, 11 | required: true, 12 | }, 13 | education: [ 14 | { 15 | institutionName: { 16 | type: String, 17 | required: true, 18 | }, 19 | startYear: { 20 | type: Number, 21 | min: 1930, 22 | max: new Date().getFullYear(), 23 | required: true, 24 | validate: Number.isInteger, 25 | }, 26 | endYear: { 27 | type: Number, 28 | max: new Date().getFullYear(), 29 | validate: [ 30 | { validator: Number.isInteger, msg: "Year should be an integer" }, 31 | { 32 | validator: function (value) { 33 | return this.startYear <= value; 34 | }, 35 | msg: "End year should be greater than or equal to Start year", 36 | }, 37 | ], 38 | }, 39 | }, 40 | ], 41 | skills: [String], 42 | rating: { 43 | type: Number, 44 | max: 5.0, 45 | default: -1.0, 46 | validate: { 47 | validator: function (v) { 48 | return v >= -1.0 && v <= 5.0; 49 | }, 50 | msg: "Invalid rating", 51 | }, 52 | }, 53 | resume: { 54 | type: String, 55 | }, 56 | profile: { 57 | type: String, 58 | }, 59 | }, 60 | { collation: { locale: "en" } } 61 | ); 62 | 63 | module.exports = mongoose.model("JobApplicantInfo", schema); 64 | -------------------------------------------------------------------------------- /backend/db/Rating.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | let schema = new mongoose.Schema( 4 | { 5 | category: { 6 | type: String, 7 | enum: ["job", "applicant"], 8 | required: true, 9 | }, 10 | receiverId: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | required: true, 13 | }, 14 | senderId: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | required: true, 17 | }, 18 | rating: { 19 | type: Number, 20 | max: 5.0, 21 | default: -1.0, 22 | validate: { 23 | validator: function (v) { 24 | return v >= -1.0 && v <= 5.0; 25 | }, 26 | msg: "Invalid rating", 27 | }, 28 | }, 29 | }, 30 | { collation: { locale: "en" } } 31 | ); 32 | 33 | schema.index({ category: 1, receiverId: 1, senderId: 1 }, { unique: true }); 34 | 35 | module.exports = mongoose.model("ratings", schema); 36 | -------------------------------------------------------------------------------- /backend/db/Recruiter.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | let schema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | }, 9 | name: { 10 | type: String, 11 | required: true, 12 | }, 13 | contactNumber: { 14 | type: String, 15 | validate: { 16 | validator: function (v) { 17 | return v !== "" ? /\+\d{1,3}\d{10}/.test(v) : true; 18 | }, 19 | msg: "Phone number is invalid!", 20 | }, 21 | }, 22 | bio: { 23 | type: String, 24 | }, 25 | }, 26 | { collation: { locale: "en" } } 27 | ); 28 | 29 | module.exports = mongoose.model("RecruiterInfo", schema); 30 | -------------------------------------------------------------------------------- /backend/db/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcrypt"); 3 | require("mongoose-type-email"); 4 | 5 | let schema = new mongoose.Schema( 6 | { 7 | email: { 8 | type: mongoose.SchemaTypes.Email, 9 | unique: true, 10 | lowercase: true, 11 | required: true, 12 | }, 13 | password: { 14 | type: String, 15 | required: true, 16 | }, 17 | type: { 18 | type: String, 19 | enum: ["recruiter", "applicant"], 20 | required: true, 21 | }, 22 | }, 23 | { collation: { locale: "en" } } 24 | ); 25 | 26 | // Password hashing 27 | schema.pre("save", function (next) { 28 | let user = this; 29 | 30 | // if the data is not modified 31 | if (!user.isModified("password")) { 32 | return next(); 33 | } 34 | 35 | bcrypt.hash(user.password, 10, (err, hash) => { 36 | if (err) { 37 | return next(err); 38 | } 39 | user.password = hash; 40 | next(); 41 | }); 42 | }); 43 | 44 | // Password verification upon login 45 | schema.methods.login = function (password) { 46 | let user = this; 47 | 48 | return new Promise((resolve, reject) => { 49 | bcrypt.compare(password, user.password, (err, result) => { 50 | if (err) { 51 | reject(err); 52 | } 53 | if (result) { 54 | resolve(); 55 | } else { 56 | reject(); 57 | } 58 | }); 59 | }); 60 | }; 61 | 62 | module.exports = mongoose.model("UserAuth", schema); 63 | -------------------------------------------------------------------------------- /backend/lib/authKeys.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jwtSecretKey: "jwt_secret", 3 | }; 4 | -------------------------------------------------------------------------------- /backend/lib/jwtAuth.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | 3 | const jwtAuth = (req, res, next) => { 4 | passport.authenticate("jwt", { session: false }, function (err, user, info) { 5 | if (err) { 6 | return next(err); 7 | } 8 | if (!user) { 9 | res.status(401).json(info); 10 | return; 11 | } 12 | req.user = user; 13 | next(); 14 | })(req, res, next); 15 | }; 16 | 17 | module.exports = jwtAuth; 18 | -------------------------------------------------------------------------------- /backend/lib/passportConfig.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const Strategy = require("passport-local").Strategy; 3 | 4 | const passportJWT = require("passport-jwt"); 5 | const JWTStrategy = passportJWT.Strategy; 6 | const ExtractJWT = passportJWT.ExtractJwt; 7 | 8 | const User = require("../db/User"); 9 | const authKeys = require("./authKeys"); 10 | 11 | const filterJson = (obj, unwantedKeys) => { 12 | const filteredObj = {}; 13 | Object.keys(obj).forEach((key) => { 14 | if (unwantedKeys.indexOf(key) === -1) { 15 | filteredObj[key] = obj[key]; 16 | } 17 | }); 18 | return filteredObj; 19 | }; 20 | 21 | passport.use( 22 | new Strategy( 23 | { 24 | usernameField: "email", 25 | passReqToCallback: true, 26 | }, 27 | (req, email, password, done, res) => { 28 | // console.log(email, password); 29 | User.findOne({ email: email }, (err, user) => { 30 | if (err) { 31 | return done(err); 32 | } 33 | if (!user) { 34 | return done(null, false, { 35 | message: "User does not exist", 36 | }); 37 | } 38 | 39 | user 40 | .login(password) 41 | .then(() => { 42 | // let userSecure = {}; 43 | // const unwantedKeys = ["password", "__v"]; 44 | // Object.keys(user["_doc"]).forEach((key) => { 45 | // if (unwantedKeys.indexOf(key) === -1) { 46 | // userSecure[key] = user[key]; 47 | // } 48 | // }); 49 | user["_doc"] = filterJson(user["_doc"], ["password", "__v"]); 50 | return done(null, user); 51 | }) 52 | .catch((err) => { 53 | return done(err, false, { 54 | message: "Password is incorrect.", 55 | }); 56 | }); 57 | }); 58 | } 59 | ) 60 | ); 61 | 62 | passport.use( 63 | new JWTStrategy( 64 | { 65 | jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), 66 | secretOrKey: authKeys.jwtSecretKey, 67 | }, 68 | (jwt_payload, done) => { 69 | User.findById(jwt_payload._id) 70 | .then((user) => { 71 | console.log(Object.keys(jwt_payload)); 72 | if (!user) { 73 | return done(null, false, { 74 | message: "JWT Token does not exist", 75 | }); 76 | } 77 | user["_doc"] = filterJson(user["_doc"], ["password", "__v"]); 78 | return done(null, user); 79 | }) 80 | .catch((err) => { 81 | return done(err, false, { 82 | message: "Incorrect Token", 83 | }); 84 | }); 85 | } 86 | ) 87 | ); 88 | 89 | module.exports = passport; 90 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "job-portal-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "npx nodemon server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.0.0", 14 | "body-parser": "^1.19.0", 15 | "connect-flash": "^0.1.1", 16 | "connect-mongo": "^3.2.0", 17 | "cors": "^2.8.5", 18 | "crypto": "^1.0.1", 19 | "express": "^4.17.1", 20 | "express-session": "^1.17.1", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^5.11.11", 23 | "mongoose-type-email": "^1.1.2", 24 | "multer": "^2.0.0-rc.2", 25 | "passport": "^0.4.1", 26 | "passport-jwt": "^4.0.0", 27 | "passport-local": "^1.0.0", 28 | "uuid": "^8.3.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const passport = require("passport"); 3 | const jwt = require("jsonwebtoken"); 4 | const authKeys = require("../lib/authKeys"); 5 | 6 | const User = require("../db/User"); 7 | const JobApplicant = require("../db/JobApplicant"); 8 | const Recruiter = require("../db/Recruiter"); 9 | 10 | const router = express.Router(); 11 | 12 | router.post("/signup", (req, res) => { 13 | const data = req.body; 14 | let user = new User({ 15 | email: data.email, 16 | password: data.password, 17 | type: data.type, 18 | }); 19 | 20 | user 21 | .save() 22 | .then(() => { 23 | const userDetails = 24 | user.type == "recruiter" 25 | ? new Recruiter({ 26 | userId: user._id, 27 | name: data.name, 28 | contactNumber: data.contactNumber, 29 | bio: data.bio, 30 | }) 31 | : new JobApplicant({ 32 | userId: user._id, 33 | name: data.name, 34 | education: data.education, 35 | skills: data.skills, 36 | rating: data.rating, 37 | resume: data.resume, 38 | profile: data.profile, 39 | }); 40 | 41 | userDetails 42 | .save() 43 | .then(() => { 44 | // Token 45 | const token = jwt.sign({ _id: user._id }, authKeys.jwtSecretKey); 46 | res.json({ 47 | token: token, 48 | type: user.type, 49 | }); 50 | }) 51 | .catch((err) => { 52 | user 53 | .delete() 54 | .then(() => { 55 | res.status(400).json(err); 56 | }) 57 | .catch((err) => { 58 | res.json({ error: err }); 59 | }); 60 | err; 61 | }); 62 | }) 63 | .catch((err) => { 64 | res.status(400).json(err); 65 | }); 66 | }); 67 | 68 | router.post("/login", (req, res, next) => { 69 | passport.authenticate( 70 | "local", 71 | { session: false }, 72 | function (err, user, info) { 73 | if (err) { 74 | return next(err); 75 | } 76 | if (!user) { 77 | res.status(401).json(info); 78 | return; 79 | } 80 | // Token 81 | const token = jwt.sign({ _id: user._id }, authKeys.jwtSecretKey); 82 | res.json({ 83 | token: token, 84 | type: user.type, 85 | }); 86 | } 87 | )(req, res, next); 88 | }); 89 | 90 | module.exports = router; 91 | -------------------------------------------------------------------------------- /backend/routes/downloadRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | const router = express.Router(); 6 | 7 | router.get("/resume/:file", (req, res) => { 8 | const address = path.join(__dirname, `../public/resume/${req.params.file}`); 9 | fs.access(address, fs.F_OK, (err) => { 10 | if (err) { 11 | res.status(404).json({ 12 | message: "File not found", 13 | }); 14 | return; 15 | } 16 | res.sendFile(address); 17 | }); 18 | }); 19 | 20 | router.get("/profile/:file", (req, res) => { 21 | const address = path.join(__dirname, `../public/profile/${req.params.file}`); 22 | fs.access(address, fs.F_OK, (err) => { 23 | if (err) { 24 | res.status(404).json({ 25 | message: "File not found", 26 | }); 27 | return; 28 | } 29 | res.sendFile(address); 30 | }); 31 | }); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /backend/routes/uploadRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const multer = require("multer"); 3 | const fs = require("fs"); 4 | const { v4: uuidv4 } = require("uuid"); 5 | const { promisify } = require("util"); 6 | 7 | const pipeline = promisify(require("stream").pipeline); 8 | 9 | const router = express.Router(); 10 | 11 | const upload = multer(); 12 | 13 | router.post("/resume", upload.single("file"), (req, res) => { 14 | const { file } = req; 15 | if (file.detectedFileExtension != ".pdf") { 16 | res.status(400).json({ 17 | message: "Invalid format", 18 | }); 19 | } else { 20 | const filename = `${uuidv4()}${file.detectedFileExtension}`; 21 | 22 | pipeline( 23 | file.stream, 24 | fs.createWriteStream(`${__dirname}/../public/resume/${filename}`) 25 | ) 26 | .then(() => { 27 | res.send({ 28 | message: "File uploaded successfully", 29 | url: `/host/resume/${filename}`, 30 | }); 31 | }) 32 | .catch((err) => { 33 | res.status(400).json({ 34 | message: "Error while uploading", 35 | }); 36 | }); 37 | } 38 | }); 39 | 40 | router.post("/profile", upload.single("file"), (req, res) => { 41 | const { file } = req; 42 | if ( 43 | file.detectedFileExtension != ".jpg" && 44 | file.detectedFileExtension != ".png" 45 | ) { 46 | res.status(400).json({ 47 | message: "Invalid format", 48 | }); 49 | } else { 50 | const filename = `${uuidv4()}${file.detectedFileExtension}`; 51 | 52 | pipeline( 53 | file.stream, 54 | fs.createWriteStream(`${__dirname}/../public/profile/${filename}`) 55 | ) 56 | .then(() => { 57 | res.send({ 58 | message: "Profile image uploaded successfully", 59 | url: `/host/profile/${filename}`, 60 | }); 61 | }) 62 | .catch((err) => { 63 | res.status(400).json({ 64 | message: "Error while uploading", 65 | }); 66 | }); 67 | } 68 | }); 69 | 70 | module.exports = router; 71 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const mongoose = require("mongoose"); 4 | const passportConfig = require("./lib/passportConfig"); 5 | const cors = require("cors"); 6 | const fs = require("fs"); 7 | 8 | // MongoDB 9 | mongoose 10 | .connect("mongodb://localhost:27017/jobPortal", { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true, 13 | useCreateIndex: true, 14 | useFindAndModify: false, 15 | }) 16 | .then((res) => console.log("Connected to DB")) 17 | .catch((err) => console.log(err)); 18 | 19 | // initialising directories 20 | if (!fs.existsSync("./public")) { 21 | fs.mkdirSync("./public"); 22 | } 23 | if (!fs.existsSync("./public/resume")) { 24 | fs.mkdirSync("./public/resume"); 25 | } 26 | if (!fs.existsSync("./public/profile")) { 27 | fs.mkdirSync("./public/profile"); 28 | } 29 | 30 | const app = express(); 31 | const port = 4444; 32 | 33 | app.use(bodyParser.json()); // support json encoded bodies 34 | app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies 35 | 36 | // Setting up middlewares 37 | app.use(cors()); 38 | app.use(express.json()); 39 | app.use(passportConfig.initialize()); 40 | 41 | // Routing 42 | app.use("/auth", require("./routes/authRoutes")); 43 | app.use("/api", require("./routes/apiRoutes")); 44 | app.use("/upload", require("./routes/uploadRoutes")); 45 | app.use("/host", require("./routes/downloadRoutes")); 46 | 47 | app.listen(port, () => { 48 | console.log(`Server started on port ${port}!`); 49 | }); 50 | -------------------------------------------------------------------------------- /dummyData: -------------------------------------------------------------------------------- 1 | 2 | POST 3 | localhost:4444/auth/signup 4 | 5 | // recruiter 6 | 7 | { 8 | "email": "john@gmail.com", 9 | "password": "test", 10 | "type": "recruiter", 11 | "name": "John Smith", 12 | "contactNumber": "+91-7979282839", 13 | "bio": "I am a recruiter from Google Inc" 14 | } 15 | 16 | { 17 | "email": "roman@live.in", 18 | "password": "test", 19 | "type": "recruiter", 20 | "name": "Roman Reigns", 21 | "contactNumber": "+91-8879456123", 22 | "bio": "I am a recruiter from Amazon Inc" 23 | } 24 | 25 | // applicant 26 | 27 | { 28 | "email": "shlok@gmail.com", 29 | "password": "test", 30 | "type": "applicant", 31 | "name": "Shlok Pandey", 32 | "education": [ 33 | { 34 | "institutionName": "DPS Azaad Nagar", 35 | "startYear": 2009, 36 | "endYear": 2018 37 | }, 38 | { 39 | "institutionName": "IIIT Sri City", 40 | "startYear": 2018, 41 | "endYear": 2020 42 | }, 43 | { 44 | "institutionName": "IIIT Hyderabad", 45 | "startYear": 2020 46 | } 47 | ], 48 | "skills": [ 49 | "JavaScript", 50 | "React JS", 51 | "Django" 52 | ] 53 | } 54 | 55 | { 56 | "email": "ash@gmail.com", 57 | "password": "test", 58 | "type": "applicant", 59 | "name": "Ashwani Shukla", 60 | "education": [ 61 | { 62 | "institutionName": "Allenhouse College", 63 | "startYear": 2019 64 | } 65 | ], 66 | "skills": [ 67 | "C", 68 | "C++", 69 | "Web Development" 70 | ] 71 | } 72 | 73 | // Login 74 | 75 | POST 76 | localhost:4444/auth/login 77 | 78 | 79 | { 80 | "email": "john@gmail.com", 81 | "password": "test", 82 | "type": "recruiter" 83 | } 84 | 85 | { 86 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzMzNWMzNWQ1NTJlMDU5NzA1YzYiLCJpYXQiOjE2MTA5Njk5MDl9.6f4qhjHIaRU_lnY74WB2mK_h7uo32tp1V4uf3DVN5GQ" 87 | } 88 | 89 | { 90 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NTk5ZTIwODQyZjA1YTE3NzU4ZjkiLCJpYXQiOjE2MTExNTk5NjZ9.DIGyjwezwheqFkex0dZSIOEKrEUXXSNka9Va2B2rYVc" 91 | } 92 | 93 | { 94 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg0Mzk1ZTUxMDIwYmI2OTVlZGEiLCJpYXQiOjE2MTExNjM3MTV9.6UlyrgwLvM0o8tXcihJVWTnnJNO0WlBXAAF8RKmlgRQ" 95 | } 96 | 97 | { 98 | "email": "roman@live.in", 99 | "password": "test", 100 | "type": "recruiter" 101 | } 102 | 103 | { 104 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzM2N2MzNWQ1NTJlMDU5NzA1YzkiLCJpYXQiOjE2MTA5Njk5NTl9.KVjgEa8U0BcZQ4pfJd1mr2h-CP6ZBOJKanFhmQQFDPo" 105 | } 106 | 107 | { 108 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NTllNDIwODQyZjA1YTE3NzU4ZmMiLCJpYXQiOjE2MTExNjAwMzZ9.FA0jrx3d1en4zP3ElDkRtUV1pulgPDIpVeXOfBTIy80" 109 | } 110 | { 111 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg1OTk1ZTUxMDIwYmI2OTVlZGMiLCJpYXQiOjE2MTExNjM3Mzd9.zLS_VltLBVTXcGqzO4jGjrZ4OagxL2U5MXjcdLn5jNY" 112 | } 113 | 114 | { 115 | "email": "shlok@gmail.com", 116 | "password": "test", 117 | "type": "applicant" 118 | } 119 | 120 | { 121 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzM4YWMzNWQ1NTJlMDU5NzA1Y2IiLCJpYXQiOjE2MTA5Njk5OTR9.8pb8nBN0npAT_uW-2OJV8DuqXDJlPy_HOjm_MdQUwLA" 122 | } 123 | 124 | { 125 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NWEwMjIwODQyZjA1YTE3NzU4ZmUiLCJpYXQiOjE2MTExNjAwNjZ9.P4NLHyah0O6ISH4LSkEAna7g-YovyM1ArnVGJ54TcEw" 126 | } 127 | { 128 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg2Yzk1ZTUxMDIwYmI2OTVlZGUiLCJpYXQiOjE2MTExNjM3NTd9.UuqeMrMGYGJ3tJtvs8Z9VgLxqIF6QUHKjU9EIbQ8Pjc" 129 | } 130 | 131 | { 132 | "email": "ash@gmail.com", 133 | "password": "test", 134 | "type": "applicant" 135 | } 136 | 137 | { 138 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzNhMWMzNWQ1NTJlMDU5NzA1ZDAiLCJpYXQiOjE2MTA5NzAwMTd9.oL617v51AErVlaPGr1oP-5ILQbA9DO2DAoQRFtLh52U" 139 | } 140 | 141 | { 142 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NWExNjIwODQyZjA1YTE3NzU5MDMiLCJpYXQiOjE2MTExNjAwODd9.YoYpauFDzF28ZzKkeItbRPdcqw8biNKujNfZCGCKibw" 143 | } 144 | 145 | { 146 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg4Mzk1ZTUxMDIwYmI2OTVlZTMiLCJpYXQiOjE2MTExNjM3Nzl9.iFPFXu3aOax6muoCzNJavGSKa9s87S5AIXTiHvJ0OSQ" 147 | } 148 | 149 | // jobs 150 | 151 | GET 152 | localhost:4444/api/jobs 153 | 154 | POST 155 | localhost:4444/api/jobs 156 | 157 | Admin 1 158 | 159 | { 160 | "title": "Software Development Intern", 161 | "maxApplicants": 5, 162 | "maxPositions": 1, 163 | "deadline": "2021-02-22T18:17:24.519Z", 164 | "skillsets": ["C", "C++", "Javascript"], 165 | "jobType": "Internship", 166 | "duration": 2, 167 | "salary": 4000 168 | } 169 | 170 | { 171 | "title": "Web Development(Full Time)", 172 | "maxApplicants": 1, 173 | "maxPositions": 1, 174 | "deadline": "2021-02-20T18:17:24.519Z", 175 | "skillsets": ["PHP", "Django"], 176 | "jobType": "Full Time", 177 | "duration": 0, 178 | "salary": 5000 179 | } 180 | 181 | Admin 2 182 | 183 | { 184 | "title": "Full Stack Intern", 185 | "maxApplicants": 5, 186 | "maxPositions": 3, 187 | "deadline": "2021-02-20T18:17:24.519Z", 188 | "skillsets": ["ReactJS", "NodeJS", "Express"], 189 | "jobType": "Internship", 190 | "duration": 5, 191 | "salary": 4500 192 | } 193 | 194 | // view a job 195 | // delete a job 196 | 197 | // update a job 198 | 199 | { 200 | "maxApplicants": 4, 201 | "maxPositions": 2, 202 | "deadline": "2021-02-21T18:17:24.519Z" 203 | } 204 | 205 | 206 | // get user personal details 207 | GET 208 | localhost:4444/api/user 209 | 210 | // update user details 211 | PUT 212 | localhost:4444/api/user 213 | 214 | { 215 | "name": "Roman Rollins", 216 | "contactNumber": "+91-8945659878", 217 | "bio": "I am a recruiter from Flipkart Inc", 218 | } 219 | 220 | // job application 221 | 222 | { 223 | "sop": "Shlok: I wouldn't let you down" 224 | } 225 | 226 | { 227 | "sop": "Ashwani: I hope you like my resume." 228 | } 229 | 230 | // update status 231 | 232 | // for client side 233 | { 234 | "status": "cancelled" 235 | } 236 | 237 | { 238 | "status": "shortlisted" 239 | } 240 | 241 | { 242 | "status": "rejected" 243 | } 244 | 245 | { 246 | "status": "accepted" 247 | } 248 | 249 | { 250 | "status": "finished" 251 | } 252 | 253 | // test rating 254 | 255 | { 256 | "applicantId": "6005738ac35d552e059705cb", 257 | "rating": 4.2 258 | } 259 | 260 | { 261 | "applicantId": "600573a1c35d552e059705d0", 262 | "rating": 3.5 263 | } 264 | 265 | { 266 | "jobId": "600576e16ce7b7325f5b5c70", 267 | "rating": 2.0 268 | } -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | 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) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | 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) 55 | 56 | ### Making a Progressive Web App 57 | 58 | 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) 59 | 60 | ### Advanced Configuration 61 | 62 | 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) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | 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) 71 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "job-portal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.2", 7 | "@material-ui/icons": "^4.11.2", 8 | "@material-ui/lab": "^4.0.0-alpha.57", 9 | "@testing-library/jest-dom": "^5.11.9", 10 | "@testing-library/react": "^11.2.3", 11 | "@testing-library/user-event": "^12.6.2", 12 | "axios": "^0.21.1", 13 | "material-ui-chip-input": "^1.1.0", 14 | "react": "^17.0.1", 15 | "react-dom": "^17.0.1", 16 | "react-phone-input-2": "^2.13.9", 17 | "react-router-dom": "^5.2.0", 18 | "react-scripts": "4.0.1", 19 | "web-vitals": "^0.2.4" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b30wulffz/job-portal/208edaa77d51ea585bdad7e53acb20dce0ecaa09/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b30wulffz/job-portal/208edaa77d51ea585bdad7e53acb20dce0ecaa09/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b30wulffz/job-portal/208edaa77d51ea585bdad7e53acb20dce0ecaa09/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b30wulffz/job-portal/208edaa77d51ea585bdad7e53acb20dce0ecaa09/frontend/src/App.css -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from "react"; 2 | import { BrowserRouter, Switch, Route } from "react-router-dom"; 3 | import { Grid, makeStyles } from "@material-ui/core"; 4 | 5 | import Welcome, { ErrorPage } from "./component/Welcome"; 6 | import Navbar from "./component/Navbar"; 7 | import Login from "./component/Login"; 8 | import Logout from "./component/Logout"; 9 | import Signup from "./component/Signup"; 10 | import Home from "./component/Home"; 11 | import Applications from "./component/Applications"; 12 | import Profile from "./component/Profile"; 13 | import CreateJobs from "./component/recruiter/CreateJobs"; 14 | import MyJobs from "./component/recruiter/MyJobs"; 15 | import JobApplications from "./component/recruiter/JobApplications"; 16 | import AcceptedApplicants from "./component/recruiter/AcceptedApplicants"; 17 | import RecruiterProfile from "./component/recruiter/Profile"; 18 | import MessagePopup from "./lib/MessagePopup"; 19 | import isAuth, { userType } from "./lib/isAuth"; 20 | 21 | const useStyles = makeStyles((theme) => ({ 22 | body: { 23 | display: "flex", 24 | flexDirection: "column", 25 | justifyContent: "center", 26 | alignItems: "center", 27 | minHeight: "98vh", 28 | paddingTop: "64px", 29 | boxSizing: "border-box", 30 | width: "100%", 31 | }, 32 | })); 33 | 34 | export const SetPopupContext = createContext(); 35 | 36 | function App() { 37 | const classes = useStyles(); 38 | const [popup, setPopup] = useState({ 39 | open: false, 40 | severity: "", 41 | message: "", 42 | }); 43 | return ( 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {userType() === "recruiter" ? ( 72 | 73 | ) : ( 74 | 75 | )} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 98 | setPopup({ 99 | ...popup, 100 | open: status, 101 | }) 102 | } 103 | severity={popup.severity} 104 | message={popup.message} 105 | /> 106 | 107 | 108 | ); 109 | } 110 | 111 | export default App; 112 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/component/Applications.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { 3 | Button, 4 | Chip, 5 | Grid, 6 | IconButton, 7 | InputAdornment, 8 | makeStyles, 9 | Paper, 10 | TextField, 11 | Typography, 12 | Modal, 13 | Slider, 14 | FormControlLabel, 15 | FormGroup, 16 | MenuItem, 17 | Checkbox, 18 | } from "@material-ui/core"; 19 | import Rating from "@material-ui/lab/Rating"; 20 | import axios from "axios"; 21 | 22 | import { SetPopupContext } from "../App"; 23 | 24 | import apiList from "../lib/apiList"; 25 | 26 | const useStyles = makeStyles((theme) => ({ 27 | body: { 28 | height: "inherit", 29 | }, 30 | statusBlock: { 31 | width: "100%", 32 | height: "100%", 33 | display: "flex", 34 | alignItems: "center", 35 | justifyContent: "center", 36 | textTransform: "uppercase", 37 | }, 38 | jobTileOuter: { 39 | padding: "30px", 40 | margin: "20px 0", 41 | boxSizing: "border-box", 42 | width: "100%", 43 | }, 44 | popupDialog: { 45 | height: "100%", 46 | display: "flex", 47 | alignItems: "center", 48 | justifyContent: "center", 49 | }, 50 | })); 51 | 52 | const ApplicationTile = (props) => { 53 | const classes = useStyles(); 54 | const { application } = props; 55 | const setPopup = useContext(SetPopupContext); 56 | const [open, setOpen] = useState(false); 57 | const [rating, setRating] = useState(application.job.rating); 58 | 59 | const appliedOn = new Date(application.dateOfApplication); 60 | const joinedOn = new Date(application.dateOfJoining); 61 | 62 | const fetchRating = () => { 63 | axios 64 | .get(`${apiList.rating}?id=${application.job._id}`, { 65 | headers: { 66 | Authorization: `Bearer ${localStorage.getItem("token")}`, 67 | }, 68 | }) 69 | .then((response) => { 70 | setRating(response.data.rating); 71 | console.log(response.data); 72 | }) 73 | .catch((err) => { 74 | // console.log(err.response); 75 | console.log(err.response.data); 76 | setPopup({ 77 | open: true, 78 | severity: "error", 79 | message: "Error", 80 | }); 81 | }); 82 | }; 83 | 84 | const changeRating = () => { 85 | axios 86 | .put( 87 | apiList.rating, 88 | { rating: rating, jobId: application.job._id }, 89 | { 90 | headers: { 91 | Authorization: `Bearer ${localStorage.getItem("token")}`, 92 | }, 93 | } 94 | ) 95 | .then((response) => { 96 | console.log(response.data); 97 | setPopup({ 98 | open: true, 99 | severity: "success", 100 | message: "Rating updated successfully", 101 | }); 102 | fetchRating(); 103 | setOpen(false); 104 | }) 105 | .catch((err) => { 106 | // console.log(err.response); 107 | console.log(err); 108 | setPopup({ 109 | open: true, 110 | severity: "error", 111 | message: err.response.data.message, 112 | }); 113 | fetchRating(); 114 | setOpen(false); 115 | }); 116 | }; 117 | 118 | const handleClose = () => { 119 | setOpen(false); 120 | }; 121 | 122 | const colorSet = { 123 | applied: "#3454D1", 124 | shortlisted: "#DC851F", 125 | accepted: "#09BC8A", 126 | rejected: "#D1345B", 127 | deleted: "#B49A67", 128 | cancelled: "#FF8484", 129 | finished: "#4EA5D9", 130 | }; 131 | 132 | return ( 133 | 134 | 135 | 136 | 137 | {application.job.title} 138 | 139 | Posted By: {application.recruiter.name} 140 | Role : {application.job.jobType} 141 | Salary : ₹ {application.job.salary} per month 142 | 143 | Duration :{" "} 144 | {application.job.duration !== 0 145 | ? `${application.job.duration} month` 146 | : `Flexible`} 147 | 148 | 149 | {application.job.skillsets.map((skill) => ( 150 | 151 | ))} 152 | 153 | Applied On: {appliedOn.toLocaleDateString()} 154 | {application.status === "accepted" || 155 | application.status === "finished" ? ( 156 | Joined On: {joinedOn.toLocaleDateString()} 157 | ) : null} 158 | 159 | 160 | 161 | 168 | {application.status} 169 | 170 | 171 | {application.status === "accepted" || 172 | application.status === "finished" ? ( 173 | 174 | 185 | 186 | ) : null} 187 | 188 | 189 | 190 | 201 | { 206 | setRating(newValue); 207 | }} 208 | /> 209 | 217 | 218 | 219 | 220 | ); 221 | }; 222 | 223 | const Applications = (props) => { 224 | const setPopup = useContext(SetPopupContext); 225 | const [applications, setApplications] = useState([]); 226 | 227 | useEffect(() => { 228 | getData(); 229 | }, []); 230 | 231 | const getData = () => { 232 | axios 233 | .get(apiList.applications, { 234 | headers: { 235 | Authorization: `Bearer ${localStorage.getItem("token")}`, 236 | }, 237 | }) 238 | .then((response) => { 239 | console.log(response.data); 240 | setApplications(response.data); 241 | }) 242 | .catch((err) => { 243 | // console.log(err.response); 244 | console.log(err.response.data); 245 | setPopup({ 246 | open: true, 247 | severity: "error", 248 | message: "Error", 249 | }); 250 | }); 251 | }; 252 | 253 | return ( 254 | 261 | 262 | Applications 263 | 264 | 273 | {applications.length > 0 ? ( 274 | applications.map((obj) => ( 275 | 276 | 277 | 278 | )) 279 | ) : ( 280 | 281 | No Applications Found 282 | 283 | )} 284 | 285 | 286 | ); 287 | }; 288 | 289 | export default Applications; 290 | -------------------------------------------------------------------------------- /frontend/src/component/Home.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { 3 | Button, 4 | Chip, 5 | Grid, 6 | IconButton, 7 | InputAdornment, 8 | makeStyles, 9 | Paper, 10 | TextField, 11 | Typography, 12 | Modal, 13 | Slider, 14 | FormControlLabel, 15 | FormGroup, 16 | MenuItem, 17 | Checkbox, 18 | } from "@material-ui/core"; 19 | import Rating from "@material-ui/lab/Rating"; 20 | import Pagination from "@material-ui/lab/Pagination"; 21 | import axios from "axios"; 22 | import SearchIcon from "@material-ui/icons/Search"; 23 | import FilterListIcon from "@material-ui/icons/FilterList"; 24 | import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; 25 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; 26 | 27 | import { SetPopupContext } from "../App"; 28 | 29 | import apiList from "../lib/apiList"; 30 | import { userType } from "../lib/isAuth"; 31 | 32 | const useStyles = makeStyles((theme) => ({ 33 | body: { 34 | height: "inherit", 35 | }, 36 | button: { 37 | width: "100%", 38 | height: "100%", 39 | }, 40 | jobTileOuter: { 41 | padding: "30px", 42 | margin: "20px 0", 43 | boxSizing: "border-box", 44 | width: "100%", 45 | }, 46 | popupDialog: { 47 | height: "100%", 48 | display: "flex", 49 | alignItems: "center", 50 | justifyContent: "center", 51 | }, 52 | })); 53 | 54 | const JobTile = (props) => { 55 | const classes = useStyles(); 56 | const { job } = props; 57 | const setPopup = useContext(SetPopupContext); 58 | 59 | const [open, setOpen] = useState(false); 60 | const [sop, setSop] = useState(""); 61 | 62 | const handleClose = () => { 63 | setOpen(false); 64 | setSop(""); 65 | }; 66 | 67 | const handleApply = () => { 68 | console.log(job._id); 69 | console.log(sop); 70 | axios 71 | .post( 72 | `${apiList.jobs}/${job._id}/applications`, 73 | { 74 | sop: sop, 75 | }, 76 | { 77 | headers: { 78 | Authorization: `Bearer ${localStorage.getItem("token")}`, 79 | }, 80 | } 81 | ) 82 | .then((response) => { 83 | setPopup({ 84 | open: true, 85 | severity: "success", 86 | message: response.data.message, 87 | }); 88 | handleClose(); 89 | }) 90 | .catch((err) => { 91 | console.log(err.response); 92 | setPopup({ 93 | open: true, 94 | severity: "error", 95 | message: err.response.data.message, 96 | }); 97 | handleClose(); 98 | }); 99 | }; 100 | 101 | const deadline = new Date(job.deadline).toLocaleDateString(); 102 | 103 | return ( 104 | 105 | 106 | 107 | 108 | {job.title} 109 | 110 | 111 | 112 | 113 | Role : {job.jobType} 114 | Salary : ₹ {job.salary} per month 115 | 116 | Duration :{" "} 117 | {job.duration !== 0 ? `${job.duration} month` : `Flexible`} 118 | 119 | Posted By : {job.recruiter.name} 120 | Application Deadline : {deadline} 121 | 122 | 123 | {job.skillsets.map((skill) => ( 124 | 125 | ))} 126 | 127 | 128 | 129 | 140 | 141 | 142 | 143 | 154 | { 162 | if ( 163 | event.target.value.split(" ").filter(function (n) { 164 | return n != ""; 165 | }).length <= 250 166 | ) { 167 | setSop(event.target.value); 168 | } 169 | }} 170 | /> 171 | 179 | 180 | 181 | 182 | ); 183 | }; 184 | 185 | const FilterPopup = (props) => { 186 | const classes = useStyles(); 187 | const { open, handleClose, searchOptions, setSearchOptions, getData } = props; 188 | return ( 189 | 190 | 197 | 198 | 199 | 200 | Job Type 201 | 202 | 209 | 210 | { 216 | setSearchOptions({ 217 | ...searchOptions, 218 | jobType: { 219 | ...searchOptions.jobType, 220 | [event.target.name]: event.target.checked, 221 | }, 222 | }); 223 | }} 224 | /> 225 | } 226 | label="Full Time" 227 | /> 228 | 229 | 230 | { 236 | setSearchOptions({ 237 | ...searchOptions, 238 | jobType: { 239 | ...searchOptions.jobType, 240 | [event.target.name]: event.target.checked, 241 | }, 242 | }); 243 | }} 244 | /> 245 | } 246 | label="Part Time" 247 | /> 248 | 249 | 250 | { 256 | setSearchOptions({ 257 | ...searchOptions, 258 | jobType: { 259 | ...searchOptions.jobType, 260 | [event.target.name]: event.target.checked, 261 | }, 262 | }); 263 | }} 264 | /> 265 | } 266 | label="Work From Home" 267 | /> 268 | 269 | 270 | 271 | 272 | 273 | Salary 274 | 275 | 276 | { 279 | return value * (100000 / 100); 280 | }} 281 | marks={[ 282 | { value: 0, label: "0" }, 283 | { value: 100, label: "100000" }, 284 | ]} 285 | value={searchOptions.salary} 286 | onChange={(event, value) => 287 | setSearchOptions({ 288 | ...searchOptions, 289 | salary: value, 290 | }) 291 | } 292 | /> 293 | 294 | 295 | 296 | 297 | Duration 298 | 299 | 300 | 307 | setSearchOptions({ 308 | ...searchOptions, 309 | duration: event.target.value, 310 | }) 311 | } 312 | > 313 | All 314 | 1 315 | 2 316 | 3 317 | 4 318 | 5 319 | 6 320 | 7 321 | 322 | 323 | 324 | 325 | 326 | Sort 327 | 328 | 329 | 337 | 338 | 342 | setSearchOptions({ 343 | ...searchOptions, 344 | sort: { 345 | ...searchOptions.sort, 346 | salary: { 347 | ...searchOptions.sort.salary, 348 | status: event.target.checked, 349 | }, 350 | }, 351 | }) 352 | } 353 | id="salary" 354 | /> 355 | 356 | 357 | 360 | 361 | 362 | { 365 | setSearchOptions({ 366 | ...searchOptions, 367 | sort: { 368 | ...searchOptions.sort, 369 | salary: { 370 | ...searchOptions.sort.salary, 371 | desc: !searchOptions.sort.salary.desc, 372 | }, 373 | }, 374 | }); 375 | }} 376 | > 377 | {searchOptions.sort.salary.desc ? ( 378 | 379 | ) : ( 380 | 381 | )} 382 | 383 | 384 | 385 | 393 | 394 | 398 | setSearchOptions({ 399 | ...searchOptions, 400 | sort: { 401 | ...searchOptions.sort, 402 | duration: { 403 | ...searchOptions.sort.duration, 404 | status: event.target.checked, 405 | }, 406 | }, 407 | }) 408 | } 409 | id="duration" 410 | /> 411 | 412 | 413 | 416 | 417 | 418 | { 421 | setSearchOptions({ 422 | ...searchOptions, 423 | sort: { 424 | ...searchOptions.sort, 425 | duration: { 426 | ...searchOptions.sort.duration, 427 | desc: !searchOptions.sort.duration.desc, 428 | }, 429 | }, 430 | }); 431 | }} 432 | > 433 | {searchOptions.sort.duration.desc ? ( 434 | 435 | ) : ( 436 | 437 | )} 438 | 439 | 440 | 441 | 449 | 450 | 454 | setSearchOptions({ 455 | ...searchOptions, 456 | sort: { 457 | ...searchOptions.sort, 458 | rating: { 459 | ...searchOptions.sort.rating, 460 | status: event.target.checked, 461 | }, 462 | }, 463 | }) 464 | } 465 | id="rating" 466 | /> 467 | 468 | 469 | 472 | 473 | 474 | { 477 | setSearchOptions({ 478 | ...searchOptions, 479 | sort: { 480 | ...searchOptions.sort, 481 | rating: { 482 | ...searchOptions.sort.rating, 483 | desc: !searchOptions.sort.rating.desc, 484 | }, 485 | }, 486 | }); 487 | }} 488 | > 489 | {searchOptions.sort.rating.desc ? ( 490 | 491 | ) : ( 492 | 493 | )} 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 509 | 510 | 511 | 512 | 513 | ); 514 | }; 515 | 516 | const Home = (props) => { 517 | const [jobs, setJobs] = useState([]); 518 | const [filterOpen, setFilterOpen] = useState(false); 519 | const [searchOptions, setSearchOptions] = useState({ 520 | query: "", 521 | jobType: { 522 | fullTime: false, 523 | partTime: false, 524 | wfh: false, 525 | }, 526 | salary: [0, 100], 527 | duration: "0", 528 | sort: { 529 | salary: { 530 | status: false, 531 | desc: false, 532 | }, 533 | duration: { 534 | status: false, 535 | desc: false, 536 | }, 537 | rating: { 538 | status: false, 539 | desc: false, 540 | }, 541 | }, 542 | }); 543 | 544 | const setPopup = useContext(SetPopupContext); 545 | useEffect(() => { 546 | getData(); 547 | }, []); 548 | 549 | const getData = () => { 550 | let searchParams = []; 551 | if (searchOptions.query !== "") { 552 | searchParams = [...searchParams, `q=${searchOptions.query}`]; 553 | } 554 | if (searchOptions.jobType.fullTime) { 555 | searchParams = [...searchParams, `jobType=Full%20Time`]; 556 | } 557 | if (searchOptions.jobType.partTime) { 558 | searchParams = [...searchParams, `jobType=Part%20Time`]; 559 | } 560 | if (searchOptions.jobType.wfh) { 561 | searchParams = [...searchParams, `jobType=Work%20From%20Home`]; 562 | } 563 | if (searchOptions.salary[0] != 0) { 564 | searchParams = [ 565 | ...searchParams, 566 | `salaryMin=${searchOptions.salary[0] * 1000}`, 567 | ]; 568 | } 569 | if (searchOptions.salary[1] != 100) { 570 | searchParams = [ 571 | ...searchParams, 572 | `salaryMax=${searchOptions.salary[1] * 1000}`, 573 | ]; 574 | } 575 | if (searchOptions.duration != "0") { 576 | searchParams = [...searchParams, `duration=${searchOptions.duration}`]; 577 | } 578 | 579 | let asc = [], 580 | desc = []; 581 | 582 | Object.keys(searchOptions.sort).forEach((obj) => { 583 | const item = searchOptions.sort[obj]; 584 | if (item.status) { 585 | if (item.desc) { 586 | desc = [...desc, `desc=${obj}`]; 587 | } else { 588 | asc = [...asc, `asc=${obj}`]; 589 | } 590 | } 591 | }); 592 | searchParams = [...searchParams, ...asc, ...desc]; 593 | const queryString = searchParams.join("&"); 594 | console.log(queryString); 595 | let address = apiList.jobs; 596 | if (queryString !== "") { 597 | address = `${address}?${queryString}`; 598 | } 599 | 600 | axios 601 | .get(address, { 602 | headers: { 603 | Authorization: `Bearer ${localStorage.getItem("token")}`, 604 | }, 605 | }) 606 | .then((response) => { 607 | console.log(response.data); 608 | setJobs( 609 | response.data.filter((obj) => { 610 | const today = new Date(); 611 | const deadline = new Date(obj.deadline); 612 | return deadline > today; 613 | }) 614 | ); 615 | }) 616 | .catch((err) => { 617 | console.log(err.response.data); 618 | setPopup({ 619 | open: true, 620 | severity: "error", 621 | message: "Error", 622 | }); 623 | }); 624 | }; 625 | 626 | return ( 627 | <> 628 | 635 | 642 | 643 | Jobs 644 | 645 | 646 | 650 | setSearchOptions({ 651 | ...searchOptions, 652 | query: event.target.value, 653 | }) 654 | } 655 | onKeyPress={(ev) => { 656 | if (ev.key === "Enter") { 657 | getData(); 658 | } 659 | }} 660 | InputProps={{ 661 | endAdornment: ( 662 | 663 | getData()}> 664 | 665 | 666 | 667 | ), 668 | }} 669 | style={{ width: "500px" }} 670 | variant="outlined" 671 | /> 672 | 673 | 674 | setFilterOpen(true)}> 675 | 676 | 677 | 678 | 679 | 680 | 688 | {jobs.length > 0 ? ( 689 | jobs.map((job) => { 690 | return ; 691 | }) 692 | ) : ( 693 | 694 | No jobs found 695 | 696 | )} 697 | 698 | {/* 699 | 700 | */} 701 | 702 | setFilterOpen(false)} 707 | getData={() => { 708 | getData(); 709 | setFilterOpen(false); 710 | }} 711 | /> 712 | 713 | ); 714 | }; 715 | 716 | export default Home; 717 | -------------------------------------------------------------------------------- /frontend/src/component/Login.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { 3 | Grid, 4 | TextField, 5 | Button, 6 | Typography, 7 | makeStyles, 8 | Paper, 9 | } from "@material-ui/core"; 10 | import axios from "axios"; 11 | import { Redirect } from "react-router-dom"; 12 | 13 | import PasswordInput from "../lib/PasswordInput"; 14 | import EmailInput from "../lib/EmailInput"; 15 | import { SetPopupContext } from "../App"; 16 | 17 | import apiList from "../lib/apiList"; 18 | import isAuth from "../lib/isAuth"; 19 | 20 | const useStyles = makeStyles((theme) => ({ 21 | body: { 22 | padding: "60px 60px", 23 | }, 24 | inputBox: { 25 | width: "300px", 26 | }, 27 | submitButton: { 28 | width: "300px", 29 | }, 30 | })); 31 | 32 | const Login = (props) => { 33 | const classes = useStyles(); 34 | const setPopup = useContext(SetPopupContext); 35 | 36 | const [loggedin, setLoggedin] = useState(isAuth()); 37 | 38 | const [loginDetails, setLoginDetails] = useState({ 39 | email: "", 40 | password: "", 41 | }); 42 | 43 | const [inputErrorHandler, setInputErrorHandler] = useState({ 44 | email: { 45 | error: false, 46 | message: "", 47 | }, 48 | password: { 49 | error: false, 50 | message: "", 51 | }, 52 | }); 53 | 54 | const handleInput = (key, value) => { 55 | setLoginDetails({ 56 | ...loginDetails, 57 | [key]: value, 58 | }); 59 | }; 60 | 61 | const handleInputError = (key, status, message) => { 62 | setInputErrorHandler({ 63 | ...inputErrorHandler, 64 | [key]: { 65 | error: status, 66 | message: message, 67 | }, 68 | }); 69 | }; 70 | 71 | const handleLogin = () => { 72 | const verified = !Object.keys(inputErrorHandler).some((obj) => { 73 | return inputErrorHandler[obj].error; 74 | }); 75 | if (verified) { 76 | axios 77 | .post(apiList.login, loginDetails) 78 | .then((response) => { 79 | localStorage.setItem("token", response.data.token); 80 | localStorage.setItem("type", response.data.type); 81 | setLoggedin(isAuth()); 82 | setPopup({ 83 | open: true, 84 | severity: "success", 85 | message: "Logged in successfully", 86 | }); 87 | console.log(response); 88 | }) 89 | .catch((err) => { 90 | setPopup({ 91 | open: true, 92 | severity: "error", 93 | message: err.response.data.message, 94 | }); 95 | console.log(err.response); 96 | }); 97 | } else { 98 | setPopup({ 99 | open: true, 100 | severity: "error", 101 | message: "Incorrect Input", 102 | }); 103 | } 104 | }; 105 | 106 | return loggedin ? ( 107 | 108 | ) : ( 109 | 110 | 111 | 112 | 113 | Login 114 | 115 | 116 | 117 | handleInput("email", event.target.value)} 121 | inputErrorHandler={inputErrorHandler} 122 | handleInputError={handleInputError} 123 | className={classes.inputBox} 124 | /> 125 | 126 | 127 | handleInput("password", event.target.value)} 131 | className={classes.inputBox} 132 | /> 133 | 134 | 135 | 143 | 144 | 145 | 146 | ); 147 | }; 148 | 149 | export default Login; 150 | -------------------------------------------------------------------------------- /frontend/src/component/Logout.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useContext } from "react"; 2 | import { Redirect } from "react-router-dom"; 3 | 4 | import { SetPopupContext } from "../App"; 5 | 6 | const Logout = (props) => { 7 | const setPopup = useContext(SetPopupContext); 8 | useEffect(() => { 9 | localStorage.removeItem("token"); 10 | localStorage.removeItem("type"); 11 | setPopup({ 12 | open: true, 13 | severity: "success", 14 | message: "Logged out successfully", 15 | }); 16 | }, []); 17 | return ; 18 | }; 19 | 20 | export default Logout; 21 | -------------------------------------------------------------------------------- /frontend/src/component/Navbar.js: -------------------------------------------------------------------------------- 1 | import { 2 | AppBar, 3 | Toolbar, 4 | Typography, 5 | Button, 6 | makeStyles, 7 | } from "@material-ui/core"; 8 | import { useHistory } from "react-router-dom"; 9 | 10 | import isAuth, { userType } from "../lib/isAuth"; 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | root: { 14 | flexGrow: 1, 15 | }, 16 | menuButton: { 17 | marginRight: theme.spacing(2), 18 | }, 19 | title: { 20 | flexGrow: 1, 21 | }, 22 | })); 23 | 24 | const Navbar = (props) => { 25 | const classes = useStyles(); 26 | let history = useHistory(); 27 | 28 | const handleClick = (location) => { 29 | console.log(location); 30 | history.push(location); 31 | }; 32 | 33 | return ( 34 | 35 | 36 | 37 | Job Portal 38 | 39 | {isAuth() ? ( 40 | userType() === "recruiter" ? ( 41 | <> 42 | 45 | 48 | 51 | 54 | 57 | 60 | 61 | ) : ( 62 | <> 63 | 66 | 72 | 75 | 78 | 79 | ) 80 | ) : ( 81 | <> 82 | 85 | 88 | 89 | )} 90 | 91 | 92 | ); 93 | }; 94 | 95 | export default Navbar; 96 | -------------------------------------------------------------------------------- /frontend/src/component/Profile.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { 3 | Button, 4 | Grid, 5 | Typography, 6 | Modal, 7 | Paper, 8 | makeStyles, 9 | TextField, 10 | } from "@material-ui/core"; 11 | import axios from "axios"; 12 | import ChipInput from "material-ui-chip-input"; 13 | import FileUploadInput from "../lib/FileUploadInput"; 14 | import DescriptionIcon from "@material-ui/icons/Description"; 15 | import FaceIcon from "@material-ui/icons/Face"; 16 | 17 | import { SetPopupContext } from "../App"; 18 | 19 | import apiList from "../lib/apiList"; 20 | 21 | const useStyles = makeStyles((theme) => ({ 22 | body: { 23 | height: "inherit", 24 | }, 25 | popupDialog: { 26 | height: "100%", 27 | display: "flex", 28 | alignItems: "center", 29 | justifyContent: "center", 30 | // padding: "30px", 31 | }, 32 | })); 33 | 34 | const MultifieldInput = (props) => { 35 | const classes = useStyles(); 36 | const { education, setEducation } = props; 37 | 38 | return ( 39 | <> 40 | {education.map((obj, key) => ( 41 | 42 | 43 | { 47 | const newEdu = [...education]; 48 | newEdu[key].institutionName = event.target.value; 49 | setEducation(newEdu); 50 | }} 51 | variant="outlined" 52 | fullWidth 53 | /> 54 | 55 | 56 | { 62 | const newEdu = [...education]; 63 | newEdu[key].startYear = event.target.value; 64 | setEducation(newEdu); 65 | }} 66 | /> 67 | 68 | 69 | { 75 | const newEdu = [...education]; 76 | newEdu[key].endYear = event.target.value; 77 | setEducation(newEdu); 78 | }} 79 | /> 80 | 81 | 82 | ))} 83 | 84 | 101 | 102 | 103 | ); 104 | }; 105 | 106 | const Profile = (props) => { 107 | const classes = useStyles(); 108 | const setPopup = useContext(SetPopupContext); 109 | const [userData, setUserData] = useState(); 110 | const [open, setOpen] = useState(false); 111 | 112 | const [profileDetails, setProfileDetails] = useState({ 113 | name: "", 114 | education: [], 115 | skills: [], 116 | resume: "", 117 | profile: "", 118 | }); 119 | 120 | const [education, setEducation] = useState([ 121 | { 122 | institutionName: "", 123 | startYear: "", 124 | endYear: "", 125 | }, 126 | ]); 127 | 128 | const handleInput = (key, value) => { 129 | setProfileDetails({ 130 | ...profileDetails, 131 | [key]: value, 132 | }); 133 | }; 134 | 135 | useEffect(() => { 136 | getData(); 137 | }, []); 138 | 139 | const getData = () => { 140 | axios 141 | .get(apiList.user, { 142 | headers: { 143 | Authorization: `Bearer ${localStorage.getItem("token")}`, 144 | }, 145 | }) 146 | .then((response) => { 147 | console.log(response.data); 148 | setProfileDetails(response.data); 149 | if (response.data.education.length > 0) { 150 | setEducation( 151 | response.data.education.map((edu) => ({ 152 | institutionName: edu.institutionName ? edu.institutionName : "", 153 | startYear: edu.startYear ? edu.startYear : "", 154 | endYear: edu.endYear ? edu.endYear : "", 155 | })) 156 | ); 157 | } 158 | }) 159 | .catch((err) => { 160 | console.log(err.response.data); 161 | setPopup({ 162 | open: true, 163 | severity: "error", 164 | message: "Error", 165 | }); 166 | }); 167 | }; 168 | 169 | const handleClose = () => { 170 | setOpen(false); 171 | }; 172 | 173 | const editDetails = () => { 174 | setOpen(true); 175 | }; 176 | 177 | const handleUpdate = () => { 178 | console.log(education); 179 | 180 | let updatedDetails = { 181 | ...profileDetails, 182 | education: education 183 | .filter((obj) => obj.institutionName.trim() !== "") 184 | .map((obj) => { 185 | if (obj["endYear"] === "") { 186 | delete obj["endYear"]; 187 | } 188 | return obj; 189 | }), 190 | }; 191 | 192 | axios 193 | .put(apiList.user, updatedDetails, { 194 | headers: { 195 | Authorization: `Bearer ${localStorage.getItem("token")}`, 196 | }, 197 | }) 198 | .then((response) => { 199 | setPopup({ 200 | open: true, 201 | severity: "success", 202 | message: response.data.message, 203 | }); 204 | getData(); 205 | }) 206 | .catch((err) => { 207 | setPopup({ 208 | open: true, 209 | severity: "error", 210 | message: err.response.data.message, 211 | }); 212 | console.log(err.response); 213 | }); 214 | setOpen(false); 215 | }; 216 | 217 | return ( 218 | <> 219 | 226 | 227 | Profile 228 | 229 | 230 | 240 | 241 | 242 | handleInput("name", event.target.value)} 246 | className={classes.inputBox} 247 | variant="outlined" 248 | fullWidth 249 | /> 250 | 251 | 255 | 256 | 263 | setProfileDetails({ 264 | ...profileDetails, 265 | skills: [...profileDetails.skills, chip], 266 | }) 267 | } 268 | onDelete={(chip, index) => { 269 | let skills = profileDetails.skills; 270 | skills.splice(index, 1); 271 | setProfileDetails({ 272 | ...profileDetails, 273 | skills: skills, 274 | }); 275 | }} 276 | fullWidth 277 | /> 278 | 279 | 280 | } 284 | uploadTo={apiList.uploadResume} 285 | handleInput={handleInput} 286 | identifier={"resume"} 287 | /> 288 | 289 | 290 | } 294 | uploadTo={apiList.uploadProfileImage} 295 | handleInput={handleInput} 296 | identifier={"profile"} 297 | /> 298 | 299 | 300 | 308 | 309 | 310 | 311 | {/* */} 312 | 313 | {/* */} 314 | 315 | ); 316 | }; 317 | 318 | export default Profile; 319 | -------------------------------------------------------------------------------- /frontend/src/component/Signup.js: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from "react"; 2 | import { 3 | Grid, 4 | TextField, 5 | Button, 6 | Typography, 7 | makeStyles, 8 | Paper, 9 | MenuItem, 10 | Input, 11 | } from "@material-ui/core"; 12 | import axios from "axios"; 13 | import { Redirect } from "react-router-dom"; 14 | import ChipInput from "material-ui-chip-input"; 15 | import DescriptionIcon from "@material-ui/icons/Description"; 16 | import FaceIcon from "@material-ui/icons/Face"; 17 | import PhoneInput from "react-phone-input-2"; 18 | import "react-phone-input-2/lib/material.css"; 19 | 20 | import PasswordInput from "../lib/PasswordInput"; 21 | import EmailInput from "../lib/EmailInput"; 22 | import FileUploadInput from "../lib/FileUploadInput"; 23 | import { SetPopupContext } from "../App"; 24 | 25 | import apiList from "../lib/apiList"; 26 | import isAuth from "../lib/isAuth"; 27 | 28 | const useStyles = makeStyles((theme) => ({ 29 | body: { 30 | padding: "60px 60px", 31 | }, 32 | inputBox: { 33 | width: "400px", 34 | }, 35 | submitButton: { 36 | width: "400px", 37 | }, 38 | })); 39 | 40 | const MultifieldInput = (props) => { 41 | const classes = useStyles(); 42 | const { education, setEducation } = props; 43 | 44 | return ( 45 | <> 46 | {education.map((obj, key) => ( 47 | 54 | 55 | { 59 | const newEdu = [...education]; 60 | newEdu[key].institutionName = event.target.value; 61 | setEducation(newEdu); 62 | }} 63 | variant="outlined" 64 | /> 65 | 66 | 67 | { 73 | const newEdu = [...education]; 74 | newEdu[key].startYear = event.target.value; 75 | setEducation(newEdu); 76 | }} 77 | /> 78 | 79 | 80 | { 86 | const newEdu = [...education]; 87 | newEdu[key].endYear = event.target.value; 88 | setEducation(newEdu); 89 | }} 90 | /> 91 | 92 | 93 | ))} 94 | 95 | 112 | 113 | 114 | ); 115 | }; 116 | 117 | const Login = (props) => { 118 | const classes = useStyles(); 119 | const setPopup = useContext(SetPopupContext); 120 | 121 | const [loggedin, setLoggedin] = useState(isAuth()); 122 | 123 | const [signupDetails, setSignupDetails] = useState({ 124 | type: "applicant", 125 | email: "", 126 | password: "", 127 | name: "", 128 | education: [], 129 | skills: [], 130 | resume: "", 131 | profile: "", 132 | bio: "", 133 | contactNumber: "", 134 | }); 135 | 136 | const [phone, setPhone] = useState(""); 137 | 138 | const [education, setEducation] = useState([ 139 | { 140 | institutionName: "", 141 | startYear: "", 142 | endYear: "", 143 | }, 144 | ]); 145 | 146 | const [inputErrorHandler, setInputErrorHandler] = useState({ 147 | email: { 148 | untouched: true, 149 | required: true, 150 | error: false, 151 | message: "", 152 | }, 153 | password: { 154 | untouched: true, 155 | required: true, 156 | error: false, 157 | message: "", 158 | }, 159 | name: { 160 | untouched: true, 161 | required: true, 162 | error: false, 163 | message: "", 164 | }, 165 | }); 166 | 167 | const handleInput = (key, value) => { 168 | setSignupDetails({ 169 | ...signupDetails, 170 | [key]: value, 171 | }); 172 | }; 173 | 174 | const handleInputError = (key, status, message) => { 175 | setInputErrorHandler({ 176 | ...inputErrorHandler, 177 | [key]: { 178 | required: true, 179 | untouched: false, 180 | error: status, 181 | message: message, 182 | }, 183 | }); 184 | }; 185 | 186 | const handleLogin = () => { 187 | const tmpErrorHandler = {}; 188 | Object.keys(inputErrorHandler).forEach((obj) => { 189 | if (inputErrorHandler[obj].required && inputErrorHandler[obj].untouched) { 190 | tmpErrorHandler[obj] = { 191 | required: true, 192 | untouched: false, 193 | error: true, 194 | message: `${obj[0].toUpperCase() + obj.substr(1)} is required`, 195 | }; 196 | } else { 197 | tmpErrorHandler[obj] = inputErrorHandler[obj]; 198 | } 199 | }); 200 | 201 | console.log(education); 202 | 203 | let updatedDetails = { 204 | ...signupDetails, 205 | education: education 206 | .filter((obj) => obj.institutionName.trim() !== "") 207 | .map((obj) => { 208 | if (obj["endYear"] === "") { 209 | delete obj["endYear"]; 210 | } 211 | return obj; 212 | }), 213 | }; 214 | 215 | setSignupDetails(updatedDetails); 216 | 217 | const verified = !Object.keys(tmpErrorHandler).some((obj) => { 218 | return tmpErrorHandler[obj].error; 219 | }); 220 | 221 | if (verified) { 222 | axios 223 | .post(apiList.signup, updatedDetails) 224 | .then((response) => { 225 | localStorage.setItem("token", response.data.token); 226 | localStorage.setItem("type", response.data.type); 227 | setLoggedin(isAuth()); 228 | setPopup({ 229 | open: true, 230 | severity: "success", 231 | message: "Logged in successfully", 232 | }); 233 | console.log(response); 234 | }) 235 | .catch((err) => { 236 | setPopup({ 237 | open: true, 238 | severity: "error", 239 | message: err.response.data.message, 240 | }); 241 | console.log(err.response); 242 | }); 243 | } else { 244 | setInputErrorHandler(tmpErrorHandler); 245 | setPopup({ 246 | open: true, 247 | severity: "error", 248 | message: "Incorrect Input", 249 | }); 250 | } 251 | }; 252 | 253 | const handleLoginRecruiter = () => { 254 | const tmpErrorHandler = {}; 255 | Object.keys(inputErrorHandler).forEach((obj) => { 256 | if (inputErrorHandler[obj].required && inputErrorHandler[obj].untouched) { 257 | tmpErrorHandler[obj] = { 258 | required: true, 259 | untouched: false, 260 | error: true, 261 | message: `${obj[0].toUpperCase() + obj.substr(1)} is required`, 262 | }; 263 | } else { 264 | tmpErrorHandler[obj] = inputErrorHandler[obj]; 265 | } 266 | }); 267 | 268 | let updatedDetails = { 269 | ...signupDetails, 270 | }; 271 | if (phone !== "") { 272 | updatedDetails = { 273 | ...signupDetails, 274 | contactNumber: `+${phone}`, 275 | }; 276 | } else { 277 | updatedDetails = { 278 | ...signupDetails, 279 | contactNumber: "", 280 | }; 281 | } 282 | 283 | setSignupDetails(updatedDetails); 284 | 285 | const verified = !Object.keys(tmpErrorHandler).some((obj) => { 286 | return tmpErrorHandler[obj].error; 287 | }); 288 | 289 | console.log(updatedDetails); 290 | 291 | if (verified) { 292 | axios 293 | .post(apiList.signup, updatedDetails) 294 | .then((response) => { 295 | localStorage.setItem("token", response.data.token); 296 | localStorage.setItem("type", response.data.type); 297 | setLoggedin(isAuth()); 298 | setPopup({ 299 | open: true, 300 | severity: "success", 301 | message: "Logged in successfully", 302 | }); 303 | console.log(response); 304 | }) 305 | .catch((err) => { 306 | setPopup({ 307 | open: true, 308 | severity: "error", 309 | message: err.response.data.message, 310 | }); 311 | console.log(err.response); 312 | }); 313 | } else { 314 | setInputErrorHandler(tmpErrorHandler); 315 | setPopup({ 316 | open: true, 317 | severity: "error", 318 | message: "Incorrect Input", 319 | }); 320 | } 321 | }; 322 | 323 | return loggedin ? ( 324 | 325 | ) : ( 326 | 327 | 328 | 329 | 330 | Signup 331 | 332 | 333 | 334 | { 341 | handleInput("type", event.target.value); 342 | }} 343 | > 344 | Applicant 345 | Recruiter 346 | 347 | 348 | 349 | handleInput("name", event.target.value)} 353 | className={classes.inputBox} 354 | error={inputErrorHandler.name.error} 355 | helperText={inputErrorHandler.name.message} 356 | onBlur={(event) => { 357 | if (event.target.value === "") { 358 | handleInputError("name", true, "Name is required"); 359 | } else { 360 | handleInputError("name", false, ""); 361 | } 362 | }} 363 | variant="outlined" 364 | /> 365 | 366 | 367 | handleInput("email", event.target.value)} 371 | inputErrorHandler={inputErrorHandler} 372 | handleInputError={handleInputError} 373 | className={classes.inputBox} 374 | required={true} 375 | /> 376 | 377 | 378 | handleInput("password", event.target.value)} 382 | className={classes.inputBox} 383 | error={inputErrorHandler.password.error} 384 | helperText={inputErrorHandler.password.message} 385 | onBlur={(event) => { 386 | if (event.target.value === "") { 387 | handleInputError("password", true, "Password is required"); 388 | } else { 389 | handleInputError("password", false, ""); 390 | } 391 | }} 392 | /> 393 | 394 | {signupDetails.type === "applicant" ? ( 395 | <> 396 | 400 | 401 | 407 | setSignupDetails({ ...signupDetails, skills: chips }) 408 | } 409 | /> 410 | 411 | 412 | } 416 | // value={files.resume} 417 | // onChange={(event) => 418 | // setFiles({ 419 | // ...files, 420 | // resume: event.target.files[0], 421 | // }) 422 | // } 423 | uploadTo={apiList.uploadResume} 424 | handleInput={handleInput} 425 | identifier={"resume"} 426 | /> 427 | 428 | 429 | } 433 | // value={files.profileImage} 434 | // onChange={(event) => 435 | // setFiles({ 436 | // ...files, 437 | // profileImage: event.target.files[0], 438 | // }) 439 | // } 440 | uploadTo={apiList.uploadProfileImage} 441 | handleInput={handleInput} 442 | identifier={"profile"} 443 | /> 444 | 445 | 446 | ) : ( 447 | <> 448 | 449 | { 457 | if ( 458 | event.target.value.split(" ").filter(function (n) { 459 | return n != ""; 460 | }).length <= 250 461 | ) { 462 | handleInput("bio", event.target.value); 463 | } 464 | }} 465 | /> 466 | 467 | 468 | setPhone(phone)} 472 | /> 473 | 474 | 475 | )} 476 | 477 | 478 | 490 | 491 | 492 | 493 | ); 494 | }; 495 | 496 | export default Login; 497 | 498 | // {/* 499 | // handleInput("tmpPassword", event.target.value)} 503 | // className={classes.inputBox} 504 | // labelWidth={140} 505 | // helperText={inputErrorHandler.tmpPassword.message} 506 | // error={inputErrorHandler.tmpPassword.error} 507 | // onBlur={(event) => { 508 | // if (event.target.value !== signupDetails.password) { 509 | // handleInputError( 510 | // "tmpPassword", 511 | // true, 512 | // "Passwords are not same." 513 | // ); 514 | // } 515 | // }} 516 | // /> 517 | // */} 518 | -------------------------------------------------------------------------------- /frontend/src/component/Welcome.js: -------------------------------------------------------------------------------- 1 | import { Grid, Typography } from "@material-ui/core"; 2 | 3 | const Welcome = (props) => { 4 | return ( 5 | 13 | 14 | Welcome to Job Portal 15 | 16 | 17 | ); 18 | }; 19 | 20 | export const ErrorPage = (props) => { 21 | return ( 22 | 30 | 31 | Error 404 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Welcome; 38 | -------------------------------------------------------------------------------- /frontend/src/component/recruiter/AcceptedApplicants.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { 3 | Button, 4 | Chip, 5 | Grid, 6 | IconButton, 7 | InputAdornment, 8 | makeStyles, 9 | Paper, 10 | TextField, 11 | Typography, 12 | Modal, 13 | Slider, 14 | FormControlLabel, 15 | FormGroup, 16 | MenuItem, 17 | Checkbox, 18 | Avatar, 19 | } from "@material-ui/core"; 20 | import { useParams } from "react-router-dom"; 21 | import Rating from "@material-ui/lab/Rating"; 22 | import axios from "axios"; 23 | import FilterListIcon from "@material-ui/icons/FilterList"; 24 | import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; 25 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; 26 | 27 | import { SetPopupContext } from "../../App"; 28 | 29 | import apiList, { server } from "../../lib/apiList"; 30 | 31 | const useStyles = makeStyles((theme) => ({ 32 | body: { 33 | height: "inherit", 34 | }, 35 | statusBlock: { 36 | width: "100%", 37 | height: "100%", 38 | display: "flex", 39 | alignItems: "center", 40 | justifyContent: "center", 41 | textTransform: "uppercase", 42 | }, 43 | jobTileOuter: { 44 | padding: "30px", 45 | margin: "20px 0", 46 | boxSizing: "border-box", 47 | width: "100%", 48 | }, 49 | popupDialog: { 50 | height: "100%", 51 | display: "flex", 52 | alignItems: "center", 53 | justifyContent: "center", 54 | }, 55 | avatar: { 56 | width: theme.spacing(17), 57 | height: theme.spacing(17), 58 | }, 59 | })); 60 | 61 | const FilterPopup = (props) => { 62 | const classes = useStyles(); 63 | const { open, handleClose, searchOptions, setSearchOptions, getData } = props; 64 | return ( 65 | 66 | 73 | 74 | {/* 75 | 76 | Application Status 77 | 78 | 85 | 86 | { 92 | setSearchOptions({ 93 | ...searchOptions, 94 | status: { 95 | ...searchOptions.status, 96 | [event.target.name]: event.target.checked, 97 | }, 98 | }); 99 | }} 100 | /> 101 | } 102 | label="Rejected" 103 | /> 104 | 105 | 106 | { 112 | setSearchOptions({ 113 | ...searchOptions, 114 | status: { 115 | ...searchOptions.status, 116 | [event.target.name]: event.target.checked, 117 | }, 118 | }); 119 | }} 120 | /> 121 | } 122 | label="Applied" 123 | /> 124 | 125 | 126 | { 132 | setSearchOptions({ 133 | ...searchOptions, 134 | status: { 135 | ...searchOptions.status, 136 | [event.target.name]: event.target.checked, 137 | }, 138 | }); 139 | }} 140 | /> 141 | } 142 | label="Shortlisted" 143 | /> 144 | 145 | 146 | */} 147 | 148 | 149 | Sort 150 | 151 | 152 | 160 | 161 | 165 | setSearchOptions({ 166 | ...searchOptions, 167 | sort: { 168 | ...searchOptions.sort, 169 | "jobApplicant.name": { 170 | ...searchOptions.sort["jobApplicant.name"], 171 | status: event.target.checked, 172 | }, 173 | }, 174 | }) 175 | } 176 | id="name" 177 | /> 178 | 179 | 180 | 183 | 184 | 185 | { 188 | setSearchOptions({ 189 | ...searchOptions, 190 | sort: { 191 | ...searchOptions.sort, 192 | "jobApplicant.name": { 193 | ...searchOptions.sort["jobApplicant.name"], 194 | desc: !searchOptions.sort["jobApplicant.name"].desc, 195 | }, 196 | }, 197 | }); 198 | }} 199 | > 200 | {searchOptions.sort["jobApplicant.name"].desc ? ( 201 | 202 | ) : ( 203 | 204 | )} 205 | 206 | 207 | 208 | 216 | 217 | 221 | setSearchOptions({ 222 | ...searchOptions, 223 | sort: { 224 | ...searchOptions.sort, 225 | "job.title": { 226 | ...searchOptions.sort["job.title"], 227 | status: event.target.checked, 228 | }, 229 | }, 230 | }) 231 | } 232 | id="jobTitle" 233 | /> 234 | 235 | 236 | 239 | 240 | 241 | { 244 | setSearchOptions({ 245 | ...searchOptions, 246 | sort: { 247 | ...searchOptions.sort, 248 | "job.title": { 249 | ...searchOptions.sort["job.title"], 250 | desc: !searchOptions.sort["job.title"].desc, 251 | }, 252 | }, 253 | }); 254 | }} 255 | > 256 | {searchOptions.sort["job.title"].desc ? ( 257 | 258 | ) : ( 259 | 260 | )} 261 | 262 | 263 | 264 | 272 | 273 | 277 | setSearchOptions({ 278 | ...searchOptions, 279 | sort: { 280 | ...searchOptions.sort, 281 | dateOfJoining: { 282 | ...searchOptions.sort.dateOfJoining, 283 | status: event.target.checked, 284 | }, 285 | }, 286 | }) 287 | } 288 | id="dateOfJoining" 289 | /> 290 | 291 | 292 | 295 | 296 | 297 | { 300 | setSearchOptions({ 301 | ...searchOptions, 302 | sort: { 303 | ...searchOptions.sort, 304 | dateOfJoining: { 305 | ...searchOptions.sort.dateOfJoining, 306 | desc: !searchOptions.sort.dateOfJoining.desc, 307 | }, 308 | }, 309 | }); 310 | }} 311 | > 312 | {searchOptions.sort.dateOfJoining.desc ? ( 313 | 314 | ) : ( 315 | 316 | )} 317 | 318 | 319 | 320 | 328 | 329 | 333 | setSearchOptions({ 334 | ...searchOptions, 335 | sort: { 336 | ...searchOptions.sort, 337 | "jobApplicant.rating": { 338 | ...searchOptions.sort[["jobApplicant.rating"]], 339 | status: event.target.checked, 340 | }, 341 | }, 342 | }) 343 | } 344 | id="rating" 345 | /> 346 | 347 | 348 | 351 | 352 | 353 | { 356 | setSearchOptions({ 357 | ...searchOptions, 358 | sort: { 359 | ...searchOptions.sort, 360 | "jobApplicant.rating": { 361 | ...searchOptions.sort["jobApplicant.rating"], 362 | desc: !searchOptions.sort["jobApplicant.rating"] 363 | .desc, 364 | }, 365 | }, 366 | }); 367 | }} 368 | > 369 | {searchOptions.sort["jobApplicant.rating"].desc ? ( 370 | 371 | ) : ( 372 | 373 | )} 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 389 | 390 | 391 | 392 | 393 | ); 394 | }; 395 | 396 | const ApplicationTile = (props) => { 397 | const classes = useStyles(); 398 | const { application, getData } = props; 399 | const setPopup = useContext(SetPopupContext); 400 | const [open, setOpen] = useState(false); 401 | const [openEndJob, setOpenEndJob] = useState(false); 402 | const [rating, setRating] = useState(application.jobApplicant.rating); 403 | 404 | const appliedOn = new Date(application.dateOfApplication); 405 | 406 | const changeRating = () => { 407 | axios 408 | .put( 409 | apiList.rating, 410 | { rating: rating, applicantId: application.jobApplicant.userId }, 411 | { 412 | headers: { 413 | Authorization: `Bearer ${localStorage.getItem("token")}`, 414 | }, 415 | } 416 | ) 417 | .then((response) => { 418 | console.log(response.data); 419 | setPopup({ 420 | open: true, 421 | severity: "success", 422 | message: "Rating updated successfully", 423 | }); 424 | // fetchRating(); 425 | getData(); 426 | setOpen(false); 427 | }) 428 | .catch((err) => { 429 | // console.log(err.response); 430 | console.log(err); 431 | setPopup({ 432 | open: true, 433 | severity: "error", 434 | message: err.response.data.message, 435 | }); 436 | // fetchRating(); 437 | getData(); 438 | setOpen(false); 439 | }); 440 | }; 441 | 442 | const handleClose = () => { 443 | setOpen(false); 444 | }; 445 | 446 | const handleCloseEndJob = () => { 447 | setOpenEndJob(false); 448 | }; 449 | 450 | const colorSet = { 451 | applied: "#3454D1", 452 | shortlisted: "#DC851F", 453 | accepted: "#09BC8A", 454 | rejected: "#D1345B", 455 | deleted: "#B49A67", 456 | cancelled: "#FF8484", 457 | finished: "#4EA5D9", 458 | }; 459 | 460 | const getResume = () => { 461 | if ( 462 | application.jobApplicant.resume && 463 | application.jobApplicant.resume !== "" 464 | ) { 465 | const address = `${server}${application.jobApplicant.resume}`; 466 | console.log(address); 467 | axios(address, { 468 | method: "GET", 469 | responseType: "blob", 470 | }) 471 | .then((response) => { 472 | const file = new Blob([response.data], { type: "application/pdf" }); 473 | const fileURL = URL.createObjectURL(file); 474 | window.open(fileURL); 475 | }) 476 | .catch((error) => { 477 | console.log(error); 478 | setPopup({ 479 | open: true, 480 | severity: "error", 481 | message: "Error", 482 | }); 483 | }); 484 | } else { 485 | setPopup({ 486 | open: true, 487 | severity: "error", 488 | message: "No resume found", 489 | }); 490 | } 491 | }; 492 | 493 | const updateStatus = (status) => { 494 | const address = `${apiList.applications}/${application._id}`; 495 | const statusData = { 496 | status: status, 497 | dateOfJoining: new Date().toISOString(), 498 | }; 499 | axios 500 | .put(address, statusData, { 501 | headers: { 502 | Authorization: `Bearer ${localStorage.getItem("token")}`, 503 | }, 504 | }) 505 | .then((response) => { 506 | setPopup({ 507 | open: true, 508 | severity: "success", 509 | message: response.data.message, 510 | }); 511 | handleCloseEndJob(); 512 | getData(); 513 | }) 514 | .catch((err) => { 515 | setPopup({ 516 | open: true, 517 | severity: "error", 518 | message: err.response.data.message, 519 | }); 520 | console.log(err.response); 521 | handleCloseEndJob(); 522 | }); 523 | }; 524 | 525 | return ( 526 | 527 | 528 | 537 | 541 | 542 | 543 | 544 | 545 | {application.jobApplicant.name} 546 | 547 | 548 | 549 | 557 | 558 | Job Title: {application.job.title} 559 | Role: {application.job.jobType} 560 | Applied On: {appliedOn.toLocaleDateString()} 561 | 562 | SOP: {application.sop !== "" ? application.sop : "Not Submitted"} 563 | 564 | 565 | {application.jobApplicant.skills.map((skill) => ( 566 | 567 | ))} 568 | 569 | 570 | 571 | 572 | 580 | 581 | 582 | {/* {buttonSet[application.status]} */} 583 | 596 | 597 | 598 | 608 | 609 | 610 | 611 | 612 | 623 | { 628 | setRating(newValue); 629 | }} 630 | /> 631 | 639 | 640 | 641 | 646 | 657 | 658 | Are you sure? 659 | 660 | 661 | 662 | 672 | 673 | 674 | 682 | 683 | 684 | 685 | 686 | 687 | ); 688 | }; 689 | 690 | const AcceptedApplicants = (props) => { 691 | const setPopup = useContext(SetPopupContext); 692 | const [applications, setApplications] = useState([]); 693 | const [filterOpen, setFilterOpen] = useState(false); 694 | const [searchOptions, setSearchOptions] = useState({ 695 | sort: { 696 | "jobApplicant.name": { 697 | status: false, 698 | desc: false, 699 | }, 700 | "job.title": { 701 | status: false, 702 | desc: false, 703 | }, 704 | dateOfJoining: { 705 | status: true, 706 | desc: true, 707 | }, 708 | "jobApplicant.rating": { 709 | status: false, 710 | desc: false, 711 | }, 712 | }, 713 | }); 714 | 715 | useEffect(() => { 716 | getData(); 717 | }, []); 718 | 719 | const getData = () => { 720 | let searchParams = []; 721 | searchParams = [...searchParams, `status=accepted`]; 722 | 723 | let asc = [], 724 | desc = []; 725 | 726 | Object.keys(searchOptions.sort).forEach((obj) => { 727 | const item = searchOptions.sort[obj]; 728 | if (item.status) { 729 | if (item.desc) { 730 | desc = [...desc, `desc=${obj}`]; 731 | } else { 732 | asc = [...asc, `asc=${obj}`]; 733 | } 734 | } 735 | }); 736 | 737 | searchParams = [...searchParams, ...asc, ...desc]; 738 | const queryString = searchParams.join("&"); 739 | console.log(queryString); 740 | let address = `${apiList.applicants}`; 741 | if (queryString !== "") { 742 | address = `${address}?${queryString}`; 743 | } 744 | 745 | console.log(address); 746 | 747 | axios 748 | .get(address, { 749 | headers: { 750 | Authorization: `Bearer ${localStorage.getItem("token")}`, 751 | }, 752 | }) 753 | .then((response) => { 754 | console.log(response.data); 755 | setApplications(response.data); 756 | }) 757 | .catch((err) => { 758 | console.log(err.response); 759 | // console.log(err.response.data); 760 | setApplications([]); 761 | setPopup({ 762 | open: true, 763 | severity: "error", 764 | message: err.response.data.message, 765 | }); 766 | }); 767 | }; 768 | 769 | return ( 770 | <> 771 | 778 | 779 | Employees 780 | 781 | 782 | setFilterOpen(true)}> 783 | 784 | 785 | 786 | 795 | {applications.length > 0 ? ( 796 | applications.map((obj) => ( 797 | 798 | {/* {console.log(obj)} */} 799 | 800 | 801 | )) 802 | ) : ( 803 | 804 | No Applications Found 805 | 806 | )} 807 | 808 | 809 | setFilterOpen(false)} 814 | getData={() => { 815 | getData(); 816 | setFilterOpen(false); 817 | }} 818 | /> 819 | 820 | ); 821 | }; 822 | 823 | export default AcceptedApplicants; 824 | -------------------------------------------------------------------------------- /frontend/src/component/recruiter/CreateJobs.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { 3 | Button, 4 | Grid, 5 | Typography, 6 | Modal, 7 | Paper, 8 | makeStyles, 9 | TextField, 10 | MenuItem, 11 | } from "@material-ui/core"; 12 | import axios from "axios"; 13 | import ChipInput from "material-ui-chip-input"; 14 | 15 | import { SetPopupContext } from "../../App"; 16 | 17 | import apiList from "../../lib/apiList"; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | body: { 21 | height: "inherit", 22 | }, 23 | popupDialog: { 24 | height: "100%", 25 | display: "flex", 26 | alignItems: "center", 27 | justifyContent: "center", 28 | // padding: "30px", 29 | }, 30 | })); 31 | 32 | const CreateJobs = (props) => { 33 | const classes = useStyles(); 34 | const setPopup = useContext(SetPopupContext); 35 | 36 | const [jobDetails, setJobDetails] = useState({ 37 | title: "", 38 | maxApplicants: 100, 39 | maxPositions: 30, 40 | deadline: new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000) 41 | .toISOString() 42 | .substr(0, 16), 43 | skillsets: [], 44 | jobType: "Full Time", 45 | duration: 0, 46 | salary: 0, 47 | }); 48 | 49 | const handleInput = (key, value) => { 50 | setJobDetails({ 51 | ...jobDetails, 52 | [key]: value, 53 | }); 54 | }; 55 | 56 | const handleUpdate = () => { 57 | console.log(jobDetails); 58 | axios 59 | .post(apiList.jobs, jobDetails, { 60 | headers: { 61 | Authorization: `Bearer ${localStorage.getItem("token")}`, 62 | }, 63 | }) 64 | .then((response) => { 65 | setPopup({ 66 | open: true, 67 | severity: "success", 68 | message: response.data.message, 69 | }); 70 | setJobDetails({ 71 | title: "", 72 | maxApplicants: 100, 73 | maxPositions: 30, 74 | deadline: new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000) 75 | .toISOString() 76 | .substr(0, 16), 77 | skillsets: [], 78 | jobType: "Full Time", 79 | duration: 0, 80 | salary: 0, 81 | }); 82 | }) 83 | .catch((err) => { 84 | setPopup({ 85 | open: true, 86 | severity: "error", 87 | message: err.response.data.message, 88 | }); 89 | console.log(err.response); 90 | }); 91 | }; 92 | 93 | return ( 94 | <> 95 | 102 | 103 | Add Job 104 | 105 | 106 | 107 | 117 | 123 | 124 | 128 | handleInput("title", event.target.value) 129 | } 130 | variant="outlined" 131 | fullWidth 132 | /> 133 | 134 | 135 | 142 | setJobDetails({ 143 | ...jobDetails, 144 | skillsets: [...jobDetails.skillsets, chip], 145 | }) 146 | } 147 | onDelete={(chip, index) => { 148 | let skillsets = jobDetails.skillsets; 149 | skillsets.splice(index, 1); 150 | setJobDetails({ 151 | ...jobDetails, 152 | skillsets: skillsets, 153 | }); 154 | }} 155 | fullWidth 156 | /> 157 | 158 | 159 | { 165 | handleInput("jobType", event.target.value); 166 | }} 167 | fullWidth 168 | > 169 | Full Time 170 | Part Time 171 | Work From Home 172 | 173 | 174 | 175 | { 181 | handleInput("duration", event.target.value); 182 | }} 183 | fullWidth 184 | > 185 | Flexible 186 | 1 Month 187 | 2 Months 188 | 3 Months 189 | 4 Months 190 | 5 Months 191 | 6 Months 192 | 193 | 194 | 195 | { 201 | handleInput("salary", event.target.value); 202 | }} 203 | InputProps={{ inputProps: { min: 0 } }} 204 | fullWidth 205 | /> 206 | 207 | 208 | { 213 | handleInput("deadline", event.target.value); 214 | }} 215 | InputLabelProps={{ 216 | shrink: true, 217 | }} 218 | variant="outlined" 219 | fullWidth 220 | /> 221 | 222 | 223 | { 229 | handleInput("maxApplicants", event.target.value); 230 | }} 231 | InputProps={{ inputProps: { min: 1 } }} 232 | fullWidth 233 | /> 234 | 235 | 236 | { 242 | handleInput("maxPositions", event.target.value); 243 | }} 244 | InputProps={{ inputProps: { min: 1 } }} 245 | fullWidth 246 | /> 247 | 248 | 249 | 257 | 258 | 259 | 260 | 261 | 262 | ); 263 | }; 264 | 265 | export default CreateJobs; 266 | -------------------------------------------------------------------------------- /frontend/src/component/recruiter/JobApplications.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { 3 | Button, 4 | Chip, 5 | Grid, 6 | IconButton, 7 | InputAdornment, 8 | makeStyles, 9 | Paper, 10 | TextField, 11 | Typography, 12 | Modal, 13 | Slider, 14 | FormControlLabel, 15 | FormGroup, 16 | MenuItem, 17 | Checkbox, 18 | Avatar, 19 | } from "@material-ui/core"; 20 | import { useParams } from "react-router-dom"; 21 | import Rating from "@material-ui/lab/Rating"; 22 | import axios from "axios"; 23 | import FilterListIcon from "@material-ui/icons/FilterList"; 24 | import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; 25 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; 26 | 27 | import { SetPopupContext } from "../../App"; 28 | 29 | import apiList, { server } from "../../lib/apiList"; 30 | 31 | const useStyles = makeStyles((theme) => ({ 32 | body: { 33 | height: "inherit", 34 | }, 35 | statusBlock: { 36 | width: "100%", 37 | height: "100%", 38 | display: "flex", 39 | alignItems: "center", 40 | justifyContent: "center", 41 | textTransform: "uppercase", 42 | }, 43 | jobTileOuter: { 44 | padding: "30px", 45 | margin: "20px 0", 46 | boxSizing: "border-box", 47 | width: "100%", 48 | }, 49 | popupDialog: { 50 | height: "100%", 51 | display: "flex", 52 | alignItems: "center", 53 | justifyContent: "center", 54 | }, 55 | avatar: { 56 | width: theme.spacing(17), 57 | height: theme.spacing(17), 58 | }, 59 | })); 60 | 61 | const FilterPopup = (props) => { 62 | const classes = useStyles(); 63 | const { open, handleClose, searchOptions, setSearchOptions, getData } = props; 64 | return ( 65 | 66 | 73 | 74 | 75 | 76 | Application Status 77 | 78 | 85 | 86 | { 92 | setSearchOptions({ 93 | ...searchOptions, 94 | status: { 95 | ...searchOptions.status, 96 | [event.target.name]: event.target.checked, 97 | }, 98 | }); 99 | }} 100 | /> 101 | } 102 | label="Rejected" 103 | /> 104 | 105 | 106 | { 112 | setSearchOptions({ 113 | ...searchOptions, 114 | status: { 115 | ...searchOptions.status, 116 | [event.target.name]: event.target.checked, 117 | }, 118 | }); 119 | }} 120 | /> 121 | } 122 | label="Applied" 123 | /> 124 | 125 | 126 | { 132 | setSearchOptions({ 133 | ...searchOptions, 134 | status: { 135 | ...searchOptions.status, 136 | [event.target.name]: event.target.checked, 137 | }, 138 | }); 139 | }} 140 | /> 141 | } 142 | label="Shortlisted" 143 | /> 144 | 145 | 146 | 147 | 148 | 149 | Sort 150 | 151 | 152 | 160 | 161 | 165 | setSearchOptions({ 166 | ...searchOptions, 167 | sort: { 168 | ...searchOptions.sort, 169 | "jobApplicant.name": { 170 | ...searchOptions.sort["jobApplicant.name"], 171 | status: event.target.checked, 172 | }, 173 | }, 174 | }) 175 | } 176 | id="name" 177 | /> 178 | 179 | 180 | 183 | 184 | 185 | { 188 | setSearchOptions({ 189 | ...searchOptions, 190 | sort: { 191 | ...searchOptions.sort, 192 | "jobApplicant.name": { 193 | ...searchOptions.sort["jobApplicant.name"], 194 | desc: !searchOptions.sort["jobApplicant.name"].desc, 195 | }, 196 | }, 197 | }); 198 | }} 199 | > 200 | {searchOptions.sort["jobApplicant.name"].desc ? ( 201 | 202 | ) : ( 203 | 204 | )} 205 | 206 | 207 | 208 | 216 | 217 | 221 | setSearchOptions({ 222 | ...searchOptions, 223 | sort: { 224 | ...searchOptions.sort, 225 | dateOfApplication: { 226 | ...searchOptions.sort.dateOfApplication, 227 | status: event.target.checked, 228 | }, 229 | }, 230 | }) 231 | } 232 | id="dateOfApplication" 233 | /> 234 | 235 | 236 | 239 | 240 | 241 | { 244 | setSearchOptions({ 245 | ...searchOptions, 246 | sort: { 247 | ...searchOptions.sort, 248 | dateOfApplication: { 249 | ...searchOptions.sort.dateOfApplication, 250 | desc: !searchOptions.sort.dateOfApplication.desc, 251 | }, 252 | }, 253 | }); 254 | }} 255 | > 256 | {searchOptions.sort.dateOfApplication.desc ? ( 257 | 258 | ) : ( 259 | 260 | )} 261 | 262 | 263 | 264 | 272 | 273 | 277 | setSearchOptions({ 278 | ...searchOptions, 279 | sort: { 280 | ...searchOptions.sort, 281 | "jobApplicant.rating": { 282 | ...searchOptions.sort[["jobApplicant.rating"]], 283 | status: event.target.checked, 284 | }, 285 | }, 286 | }) 287 | } 288 | id="rating" 289 | /> 290 | 291 | 292 | 295 | 296 | 297 | { 300 | setSearchOptions({ 301 | ...searchOptions, 302 | sort: { 303 | ...searchOptions.sort, 304 | "jobApplicant.rating": { 305 | ...searchOptions.sort["jobApplicant.rating"], 306 | desc: !searchOptions.sort["jobApplicant.rating"] 307 | .desc, 308 | }, 309 | }, 310 | }); 311 | }} 312 | > 313 | {searchOptions.sort["jobApplicant.rating"].desc ? ( 314 | 315 | ) : ( 316 | 317 | )} 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 333 | 334 | 335 | 336 | 337 | ); 338 | }; 339 | 340 | const ApplicationTile = (props) => { 341 | const classes = useStyles(); 342 | const { application, getData } = props; 343 | const setPopup = useContext(SetPopupContext); 344 | const [open, setOpen] = useState(false); 345 | 346 | const appliedOn = new Date(application.dateOfApplication); 347 | 348 | const handleClose = () => { 349 | setOpen(false); 350 | }; 351 | 352 | const colorSet = { 353 | applied: "#3454D1", 354 | shortlisted: "#DC851F", 355 | accepted: "#09BC8A", 356 | rejected: "#D1345B", 357 | deleted: "#B49A67", 358 | cancelled: "#FF8484", 359 | finished: "#4EA5D9", 360 | }; 361 | 362 | const getResume = () => { 363 | if ( 364 | application.jobApplicant.resume && 365 | application.jobApplicant.resume !== "" 366 | ) { 367 | const address = `${server}${application.jobApplicant.resume}`; 368 | console.log(address); 369 | axios(address, { 370 | method: "GET", 371 | responseType: "blob", 372 | }) 373 | .then((response) => { 374 | const file = new Blob([response.data], { type: "application/pdf" }); 375 | const fileURL = URL.createObjectURL(file); 376 | window.open(fileURL); 377 | }) 378 | .catch((error) => { 379 | console.log(error); 380 | setPopup({ 381 | open: true, 382 | severity: "error", 383 | message: "Error", 384 | }); 385 | }); 386 | } else { 387 | setPopup({ 388 | open: true, 389 | severity: "error", 390 | message: "No resume found", 391 | }); 392 | } 393 | }; 394 | 395 | const updateStatus = (status) => { 396 | const address = `${apiList.applications}/${application._id}`; 397 | const statusData = { 398 | status: status, 399 | dateOfJoining: new Date().toISOString(), 400 | }; 401 | axios 402 | .put(address, statusData, { 403 | headers: { 404 | Authorization: `Bearer ${localStorage.getItem("token")}`, 405 | }, 406 | }) 407 | .then((response) => { 408 | setPopup({ 409 | open: true, 410 | severity: "success", 411 | message: response.data.message, 412 | }); 413 | getData(); 414 | }) 415 | .catch((err) => { 416 | setPopup({ 417 | open: true, 418 | severity: "error", 419 | message: err.response.data.message, 420 | }); 421 | console.log(err.response); 422 | }); 423 | }; 424 | 425 | const buttonSet = { 426 | applied: ( 427 | <> 428 | 429 | 439 | 440 | 441 | 451 | 452 | 453 | ), 454 | shortlisted: ( 455 | <> 456 | 457 | 467 | 468 | 469 | 479 | 480 | 481 | ), 482 | rejected: ( 483 | <> 484 | 485 | 492 | Rejected 493 | 494 | 495 | 496 | ), 497 | accepted: ( 498 | <> 499 | 500 | 507 | Accepted 508 | 509 | 510 | 511 | ), 512 | cancelled: ( 513 | <> 514 | 515 | 522 | Cancelled 523 | 524 | 525 | 526 | ), 527 | finished: ( 528 | <> 529 | 530 | 537 | Finished 538 | 539 | 540 | 541 | ), 542 | }; 543 | 544 | return ( 545 | 546 | 547 | 556 | 560 | 561 | 562 | 563 | 564 | {application.jobApplicant.name} 565 | 566 | 567 | 568 | 576 | 577 | Applied On: {appliedOn.toLocaleDateString()} 578 | 579 | Education:{" "} 580 | {application.jobApplicant.education 581 | .map((edu) => { 582 | return `${edu.institutionName} (${edu.startYear}-${ 583 | edu.endYear ? edu.endYear : "Ongoing" 584 | })`; 585 | }) 586 | .join(", ")} 587 | 588 | 589 | SOP: {application.sop !== "" ? application.sop : "Not Submitted"} 590 | 591 | 592 | {application.jobApplicant.skills.map((skill) => ( 593 | 594 | ))} 595 | 596 | 597 | 598 | 599 | 607 | 608 | 609 | {buttonSet[application.status]} 610 | 611 | 612 | 613 | 614 | 625 | 633 | 634 | 635 | 636 | ); 637 | }; 638 | 639 | const JobApplications = (props) => { 640 | const setPopup = useContext(SetPopupContext); 641 | const [applications, setApplications] = useState([]); 642 | const { jobId } = useParams(); 643 | const [filterOpen, setFilterOpen] = useState(false); 644 | const [searchOptions, setSearchOptions] = useState({ 645 | status: { 646 | all: false, 647 | applied: false, 648 | shortlisted: false, 649 | }, 650 | sort: { 651 | "jobApplicant.name": { 652 | status: false, 653 | desc: false, 654 | }, 655 | dateOfApplication: { 656 | status: true, 657 | desc: true, 658 | }, 659 | "jobApplicant.rating": { 660 | status: false, 661 | desc: false, 662 | }, 663 | }, 664 | }); 665 | 666 | useEffect(() => { 667 | getData(); 668 | }, []); 669 | 670 | const getData = () => { 671 | let searchParams = []; 672 | 673 | if (searchOptions.status.rejected) { 674 | searchParams = [...searchParams, `status=rejected`]; 675 | } 676 | if (searchOptions.status.applied) { 677 | searchParams = [...searchParams, `status=applied`]; 678 | } 679 | if (searchOptions.status.shortlisted) { 680 | searchParams = [...searchParams, `status=shortlisted`]; 681 | } 682 | 683 | let asc = [], 684 | desc = []; 685 | 686 | Object.keys(searchOptions.sort).forEach((obj) => { 687 | const item = searchOptions.sort[obj]; 688 | if (item.status) { 689 | if (item.desc) { 690 | desc = [...desc, `desc=${obj}`]; 691 | } else { 692 | asc = [...asc, `asc=${obj}`]; 693 | } 694 | } 695 | }); 696 | searchParams = [...searchParams, ...asc, ...desc]; 697 | const queryString = searchParams.join("&"); 698 | console.log(queryString); 699 | let address = `${apiList.applicants}?jobId=${jobId}`; 700 | if (queryString !== "") { 701 | address = `${address}&${queryString}`; 702 | } 703 | 704 | console.log(address); 705 | 706 | axios 707 | .get(address, { 708 | headers: { 709 | Authorization: `Bearer ${localStorage.getItem("token")}`, 710 | }, 711 | }) 712 | .then((response) => { 713 | console.log(response.data); 714 | setApplications(response.data); 715 | }) 716 | .catch((err) => { 717 | console.log(err.response); 718 | // console.log(err.response.data); 719 | setApplications([]); 720 | setPopup({ 721 | open: true, 722 | severity: "error", 723 | message: err.response.data.message, 724 | }); 725 | }); 726 | }; 727 | 728 | return ( 729 | <> 730 | 737 | 738 | Applications 739 | 740 | 741 | setFilterOpen(true)}> 742 | 743 | 744 | 745 | 754 | {applications.length > 0 ? ( 755 | applications.map((obj) => ( 756 | 757 | {/* {console.log(obj)} */} 758 | 759 | 760 | )) 761 | ) : ( 762 | 763 | No Applications Found 764 | 765 | )} 766 | 767 | 768 | setFilterOpen(false)} 773 | getData={() => { 774 | getData(); 775 | setFilterOpen(false); 776 | }} 777 | /> 778 | 779 | ); 780 | }; 781 | 782 | export default JobApplications; 783 | -------------------------------------------------------------------------------- /frontend/src/component/recruiter/MyJobs.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from "react"; 2 | import { 3 | Button, 4 | Chip, 5 | Grid, 6 | IconButton, 7 | InputAdornment, 8 | makeStyles, 9 | Paper, 10 | TextField, 11 | Typography, 12 | Modal, 13 | Slider, 14 | FormControlLabel, 15 | FormGroup, 16 | MenuItem, 17 | Checkbox, 18 | } from "@material-ui/core"; 19 | import { useHistory } from "react-router-dom"; 20 | import Rating from "@material-ui/lab/Rating"; 21 | import Pagination from "@material-ui/lab/Pagination"; 22 | import axios from "axios"; 23 | import SearchIcon from "@material-ui/icons/Search"; 24 | import FilterListIcon from "@material-ui/icons/FilterList"; 25 | import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; 26 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; 27 | 28 | import { SetPopupContext } from "../../App"; 29 | 30 | import apiList from "../../lib/apiList"; 31 | 32 | const useStyles = makeStyles((theme) => ({ 33 | body: { 34 | height: "inherit", 35 | }, 36 | button: { 37 | width: "100%", 38 | height: "100%", 39 | }, 40 | jobTileOuter: { 41 | padding: "30px", 42 | margin: "20px 0", 43 | boxSizing: "border-box", 44 | width: "100%", 45 | }, 46 | popupDialog: { 47 | height: "100%", 48 | display: "flex", 49 | alignItems: "center", 50 | justifyContent: "center", 51 | }, 52 | statusBlock: { 53 | width: "100%", 54 | height: "100%", 55 | display: "flex", 56 | alignItems: "center", 57 | justifyContent: "center", 58 | textTransform: "uppercase", 59 | }, 60 | })); 61 | 62 | const JobTile = (props) => { 63 | const classes = useStyles(); 64 | let history = useHistory(); 65 | const { job, getData } = props; 66 | const setPopup = useContext(SetPopupContext); 67 | 68 | const [open, setOpen] = useState(false); 69 | const [openUpdate, setOpenUpdate] = useState(false); 70 | const [jobDetails, setJobDetails] = useState(job); 71 | 72 | console.log(jobDetails); 73 | 74 | const handleInput = (key, value) => { 75 | setJobDetails({ 76 | ...jobDetails, 77 | [key]: value, 78 | }); 79 | }; 80 | 81 | const handleClick = (location) => { 82 | history.push(location); 83 | }; 84 | 85 | const handleClose = () => { 86 | setOpen(false); 87 | }; 88 | 89 | const handleCloseUpdate = () => { 90 | setOpenUpdate(false); 91 | }; 92 | 93 | const handleDelete = () => { 94 | console.log(job._id); 95 | axios 96 | .delete(`${apiList.jobs}/${job._id}`, { 97 | headers: { 98 | Authorization: `Bearer ${localStorage.getItem("token")}`, 99 | }, 100 | }) 101 | .then((response) => { 102 | setPopup({ 103 | open: true, 104 | severity: "success", 105 | message: response.data.message, 106 | }); 107 | getData(); 108 | handleClose(); 109 | }) 110 | .catch((err) => { 111 | console.log(err.response); 112 | setPopup({ 113 | open: true, 114 | severity: "error", 115 | message: err.response.data.message, 116 | }); 117 | handleClose(); 118 | }); 119 | }; 120 | 121 | const handleJobUpdate = () => { 122 | axios 123 | .put(`${apiList.jobs}/${job._id}`, jobDetails, { 124 | headers: { 125 | Authorization: `Bearer ${localStorage.getItem("token")}`, 126 | }, 127 | }) 128 | .then((response) => { 129 | setPopup({ 130 | open: true, 131 | severity: "success", 132 | message: response.data.message, 133 | }); 134 | getData(); 135 | handleCloseUpdate(); 136 | }) 137 | .catch((err) => { 138 | console.log(err.response); 139 | setPopup({ 140 | open: true, 141 | severity: "error", 142 | message: err.response.data.message, 143 | }); 144 | handleCloseUpdate(); 145 | }); 146 | }; 147 | 148 | const postedOn = new Date(job.dateOfPosting); 149 | 150 | return ( 151 | 152 | 153 | 154 | 155 | {job.title} 156 | 157 | 158 | 159 | 160 | Role : {job.jobType} 161 | Salary : ₹ {job.salary} per month 162 | 163 | Duration :{" "} 164 | {job.duration !== 0 ? `${job.duration} month` : `Flexible`} 165 | 166 | Date Of Posting: {postedOn.toLocaleDateString()} 167 | Number of Applicants: {job.maxApplicants} 168 | 169 | Remaining Number of Positions:{" "} 170 | {job.maxPositions - job.acceptedCandidates} 171 | 172 | 173 | {job.skillsets.map((skill) => ( 174 | 175 | ))} 176 | 177 | 178 | 179 | 180 | 188 | 189 | 190 | 203 | 204 | 205 | 215 | 216 | 217 | 218 | 219 | 230 | 231 | Are you sure? 232 | 233 | 234 | 235 | 243 | 244 | 245 | 253 | 254 | 255 | 256 | 257 | 262 | 273 | 274 | Update Details 275 | 276 | 282 | 283 | { 288 | handleInput("deadline", event.target.value); 289 | }} 290 | InputLabelProps={{ 291 | shrink: true, 292 | }} 293 | variant="outlined" 294 | fullWidth 295 | /> 296 | 297 | 298 | { 304 | handleInput("maxApplicants", event.target.value); 305 | }} 306 | InputProps={{ inputProps: { min: 1 } }} 307 | fullWidth 308 | /> 309 | 310 | 311 | { 317 | handleInput("maxPositions", event.target.value); 318 | }} 319 | InputProps={{ inputProps: { min: 1 } }} 320 | fullWidth 321 | /> 322 | 323 | 324 | 325 | 326 | 334 | 335 | 336 | 344 | 345 | 346 | 347 | 348 | 349 | ); 350 | }; 351 | 352 | const FilterPopup = (props) => { 353 | const classes = useStyles(); 354 | const { open, handleClose, searchOptions, setSearchOptions, getData } = props; 355 | return ( 356 | 357 | 364 | 365 | 366 | 367 | Job Type 368 | 369 | 376 | 377 | { 383 | setSearchOptions({ 384 | ...searchOptions, 385 | jobType: { 386 | ...searchOptions.jobType, 387 | [event.target.name]: event.target.checked, 388 | }, 389 | }); 390 | }} 391 | /> 392 | } 393 | label="Full Time" 394 | /> 395 | 396 | 397 | { 403 | setSearchOptions({ 404 | ...searchOptions, 405 | jobType: { 406 | ...searchOptions.jobType, 407 | [event.target.name]: event.target.checked, 408 | }, 409 | }); 410 | }} 411 | /> 412 | } 413 | label="Part Time" 414 | /> 415 | 416 | 417 | { 423 | setSearchOptions({ 424 | ...searchOptions, 425 | jobType: { 426 | ...searchOptions.jobType, 427 | [event.target.name]: event.target.checked, 428 | }, 429 | }); 430 | }} 431 | /> 432 | } 433 | label="Work From Home" 434 | /> 435 | 436 | 437 | 438 | 439 | 440 | Salary 441 | 442 | 443 | { 446 | return value * (100000 / 100); 447 | }} 448 | marks={[ 449 | { value: 0, label: "0" }, 450 | { value: 100, label: "100000" }, 451 | ]} 452 | value={searchOptions.salary} 453 | onChange={(event, value) => 454 | setSearchOptions({ 455 | ...searchOptions, 456 | salary: value, 457 | }) 458 | } 459 | /> 460 | 461 | 462 | 463 | 464 | Duration 465 | 466 | 467 | 474 | setSearchOptions({ 475 | ...searchOptions, 476 | duration: event.target.value, 477 | }) 478 | } 479 | > 480 | All 481 | 1 482 | 2 483 | 3 484 | 4 485 | 5 486 | 6 487 | 7 488 | 489 | 490 | 491 | 492 | 493 | Sort 494 | 495 | 496 | 504 | 505 | 509 | setSearchOptions({ 510 | ...searchOptions, 511 | sort: { 512 | ...searchOptions.sort, 513 | salary: { 514 | ...searchOptions.sort.salary, 515 | status: event.target.checked, 516 | }, 517 | }, 518 | }) 519 | } 520 | id="salary" 521 | /> 522 | 523 | 524 | 527 | 528 | 529 | { 532 | setSearchOptions({ 533 | ...searchOptions, 534 | sort: { 535 | ...searchOptions.sort, 536 | salary: { 537 | ...searchOptions.sort.salary, 538 | desc: !searchOptions.sort.salary.desc, 539 | }, 540 | }, 541 | }); 542 | }} 543 | > 544 | {searchOptions.sort.salary.desc ? ( 545 | 546 | ) : ( 547 | 548 | )} 549 | 550 | 551 | 552 | 560 | 561 | 565 | setSearchOptions({ 566 | ...searchOptions, 567 | sort: { 568 | ...searchOptions.sort, 569 | duration: { 570 | ...searchOptions.sort.duration, 571 | status: event.target.checked, 572 | }, 573 | }, 574 | }) 575 | } 576 | id="duration" 577 | /> 578 | 579 | 580 | 583 | 584 | 585 | { 588 | setSearchOptions({ 589 | ...searchOptions, 590 | sort: { 591 | ...searchOptions.sort, 592 | duration: { 593 | ...searchOptions.sort.duration, 594 | desc: !searchOptions.sort.duration.desc, 595 | }, 596 | }, 597 | }); 598 | }} 599 | > 600 | {searchOptions.sort.duration.desc ? ( 601 | 602 | ) : ( 603 | 604 | )} 605 | 606 | 607 | 608 | 616 | 617 | 621 | setSearchOptions({ 622 | ...searchOptions, 623 | sort: { 624 | ...searchOptions.sort, 625 | rating: { 626 | ...searchOptions.sort.rating, 627 | status: event.target.checked, 628 | }, 629 | }, 630 | }) 631 | } 632 | id="rating" 633 | /> 634 | 635 | 636 | 639 | 640 | 641 | { 644 | setSearchOptions({ 645 | ...searchOptions, 646 | sort: { 647 | ...searchOptions.sort, 648 | rating: { 649 | ...searchOptions.sort.rating, 650 | desc: !searchOptions.sort.rating.desc, 651 | }, 652 | }, 653 | }); 654 | }} 655 | > 656 | {searchOptions.sort.rating.desc ? ( 657 | 658 | ) : ( 659 | 660 | )} 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 676 | 677 | 678 | 679 | 680 | ); 681 | }; 682 | 683 | const MyJobs = (props) => { 684 | const [jobs, setJobs] = useState([]); 685 | const [filterOpen, setFilterOpen] = useState(false); 686 | const [searchOptions, setSearchOptions] = useState({ 687 | query: "", 688 | jobType: { 689 | fullTime: false, 690 | partTime: false, 691 | wfh: false, 692 | }, 693 | salary: [0, 100], 694 | duration: "0", 695 | sort: { 696 | salary: { 697 | status: false, 698 | desc: false, 699 | }, 700 | duration: { 701 | status: false, 702 | desc: false, 703 | }, 704 | rating: { 705 | status: false, 706 | desc: false, 707 | }, 708 | }, 709 | }); 710 | 711 | const setPopup = useContext(SetPopupContext); 712 | useEffect(() => { 713 | getData(); 714 | }, []); 715 | 716 | const getData = () => { 717 | let searchParams = [`myjobs=1`]; 718 | if (searchOptions.query !== "") { 719 | searchParams = [...searchParams, `q=${searchOptions.query}`]; 720 | } 721 | if (searchOptions.jobType.fullTime) { 722 | searchParams = [...searchParams, `jobType=Full%20Time`]; 723 | } 724 | if (searchOptions.jobType.partTime) { 725 | searchParams = [...searchParams, `jobType=Part%20Time`]; 726 | } 727 | if (searchOptions.jobType.wfh) { 728 | searchParams = [...searchParams, `jobType=Work%20From%20Home`]; 729 | } 730 | if (searchOptions.salary[0] != 0) { 731 | searchParams = [ 732 | ...searchParams, 733 | `salaryMin=${searchOptions.salary[0] * 1000}`, 734 | ]; 735 | } 736 | if (searchOptions.salary[1] != 100) { 737 | searchParams = [ 738 | ...searchParams, 739 | `salaryMax=${searchOptions.salary[1] * 1000}`, 740 | ]; 741 | } 742 | if (searchOptions.duration != "0") { 743 | searchParams = [...searchParams, `duration=${searchOptions.duration}`]; 744 | } 745 | 746 | let asc = [], 747 | desc = []; 748 | 749 | Object.keys(searchOptions.sort).forEach((obj) => { 750 | const item = searchOptions.sort[obj]; 751 | if (item.status) { 752 | if (item.desc) { 753 | desc = [...desc, `desc=${obj}`]; 754 | } else { 755 | asc = [...asc, `asc=${obj}`]; 756 | } 757 | } 758 | }); 759 | searchParams = [...searchParams, ...asc, ...desc]; 760 | const queryString = searchParams.join("&"); 761 | console.log(queryString); 762 | let address = apiList.jobs; 763 | if (queryString !== "") { 764 | address = `${address}?${queryString}`; 765 | } 766 | 767 | console.log(address); 768 | axios 769 | .get(address, { 770 | headers: { 771 | Authorization: `Bearer ${localStorage.getItem("token")}`, 772 | }, 773 | }) 774 | .then((response) => { 775 | console.log(response.data); 776 | setJobs(response.data); 777 | }) 778 | .catch((err) => { 779 | console.log(err.response.data); 780 | setPopup({ 781 | open: true, 782 | severity: "error", 783 | message: "Error", 784 | }); 785 | }); 786 | }; 787 | 788 | return ( 789 | <> 790 | 797 | 804 | 805 | My Jobs 806 | 807 | 808 | 812 | setSearchOptions({ 813 | ...searchOptions, 814 | query: event.target.value, 815 | }) 816 | } 817 | onKeyPress={(ev) => { 818 | if (ev.key === "Enter") { 819 | getData(); 820 | } 821 | }} 822 | InputProps={{ 823 | endAdornment: ( 824 | 825 | getData()}> 826 | 827 | 828 | 829 | ), 830 | }} 831 | style={{ width: "500px" }} 832 | variant="outlined" 833 | /> 834 | 835 | 836 | setFilterOpen(true)}> 837 | 838 | 839 | 840 | 841 | 842 | 850 | {jobs.length > 0 ? ( 851 | jobs.map((job) => { 852 | return ; 853 | }) 854 | ) : ( 855 | 856 | No jobs found 857 | 858 | )} 859 | 860 | 861 | setFilterOpen(false)} 866 | getData={() => { 867 | getData(); 868 | setFilterOpen(false); 869 | }} 870 | /> 871 | 872 | ); 873 | }; 874 | 875 | export default MyJobs; 876 | -------------------------------------------------------------------------------- /frontend/src/component/recruiter/Profile.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { 3 | Button, 4 | Grid, 5 | Typography, 6 | Modal, 7 | Paper, 8 | makeStyles, 9 | TextField, 10 | } from "@material-ui/core"; 11 | import axios from "axios"; 12 | import PhoneInput from "react-phone-input-2"; 13 | import "react-phone-input-2/lib/material.css"; 14 | 15 | import { SetPopupContext } from "../../App"; 16 | 17 | import apiList from "../../lib/apiList"; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | body: { 21 | height: "inherit", 22 | }, 23 | popupDialog: { 24 | height: "100%", 25 | display: "flex", 26 | alignItems: "center", 27 | justifyContent: "center", 28 | // padding: "30px", 29 | }, 30 | })); 31 | 32 | const Profile = (props) => { 33 | const classes = useStyles(); 34 | const setPopup = useContext(SetPopupContext); 35 | 36 | const [profileDetails, setProfileDetails] = useState({ 37 | name: "", 38 | bio: "", 39 | contactNumber: "", 40 | }); 41 | 42 | const [phone, setPhone] = useState(""); 43 | 44 | const handleInput = (key, value) => { 45 | setProfileDetails({ 46 | ...profileDetails, 47 | [key]: value, 48 | }); 49 | }; 50 | 51 | useEffect(() => { 52 | getData(); 53 | }, []); 54 | 55 | const getData = () => { 56 | axios 57 | .get(apiList.user, { 58 | headers: { 59 | Authorization: `Bearer ${localStorage.getItem("token")}`, 60 | }, 61 | }) 62 | .then((response) => { 63 | console.log(response.data); 64 | setProfileDetails(response.data); 65 | setPhone(response.data.contactNumber); 66 | }) 67 | .catch((err) => { 68 | console.log(err.response.data); 69 | setPopup({ 70 | open: true, 71 | severity: "error", 72 | message: "Error", 73 | }); 74 | }); 75 | }; 76 | 77 | const handleUpdate = () => { 78 | let updatedDetails = { 79 | ...profileDetails, 80 | }; 81 | if (phone !== "") { 82 | updatedDetails = { 83 | ...profileDetails, 84 | contactNumber: `+${phone}`, 85 | }; 86 | } else { 87 | updatedDetails = { 88 | ...profileDetails, 89 | contactNumber: "", 90 | }; 91 | } 92 | 93 | axios 94 | .put(apiList.user, updatedDetails, { 95 | headers: { 96 | Authorization: `Bearer ${localStorage.getItem("token")}`, 97 | }, 98 | }) 99 | .then((response) => { 100 | setPopup({ 101 | open: true, 102 | severity: "success", 103 | message: response.data.message, 104 | }); 105 | getData(); 106 | }) 107 | .catch((err) => { 108 | setPopup({ 109 | open: true, 110 | severity: "error", 111 | message: err.response.data.message, 112 | }); 113 | console.log(err.response); 114 | }); 115 | }; 116 | 117 | return ( 118 | <> 119 | 126 | 127 | Profile 128 | 129 | 130 | 141 | 142 | 143 | handleInput("name", event.target.value)} 147 | className={classes.inputBox} 148 | variant="outlined" 149 | fullWidth 150 | style={{ width: "100%" }} 151 | /> 152 | 153 | 154 | { 162 | if ( 163 | event.target.value.split(" ").filter(function (n) { 164 | return n != ""; 165 | }).length <= 250 166 | ) { 167 | handleInput("bio", event.target.value); 168 | } 169 | }} 170 | /> 171 | 172 | 179 | setPhone(phone)} 183 | style={{ width: "auto" }} 184 | /> 185 | 186 | 187 | 195 | 196 | 197 | 198 | 199 | ); 200 | }; 201 | 202 | export default Profile; 203 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /frontend/src/lib/EmailInput.js: -------------------------------------------------------------------------------- 1 | import { TextField } from "@material-ui/core"; 2 | 3 | const EmailInput = (props) => { 4 | const { 5 | label, 6 | value, 7 | onChange, 8 | inputErrorHandler, 9 | handleInputError, 10 | required, 11 | className, 12 | } = props; 13 | 14 | return ( 15 | { 22 | if (event.target.value === "") { 23 | if (required) { 24 | handleInputError("email", true, "Email is required"); 25 | } else { 26 | handleInputError("email", false, ""); 27 | } 28 | } else { 29 | const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 30 | if (re.test(String(event.target.value).toLowerCase())) { 31 | handleInputError("email", false, ""); 32 | } else { 33 | handleInputError("email", true, "Incorrect email format"); 34 | } 35 | } 36 | }} 37 | error={inputErrorHandler.email.error} 38 | className={className} 39 | /> 40 | ); 41 | }; 42 | 43 | export default EmailInput; 44 | -------------------------------------------------------------------------------- /frontend/src/lib/FileUploadInput.js: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from "react"; 2 | import { Grid, Button, TextField, LinearProgress } from "@material-ui/core"; 3 | import { CloudUpload } from "@material-ui/icons"; 4 | import Axios from "axios"; 5 | 6 | import { SetPopupContext } from "../App"; 7 | 8 | const FileUploadInput = (props) => { 9 | const setPopup = useContext(SetPopupContext); 10 | 11 | const { uploadTo, identifier, handleInput } = props; 12 | 13 | const [file, setFile] = useState(""); 14 | const [uploadPercentage, setUploadPercentage] = useState(0); 15 | 16 | const handleUpload = () => { 17 | console.log(file); 18 | const data = new FormData(); 19 | data.append("file", file); 20 | Axios.post(uploadTo, data, { 21 | headers: { 22 | "Content-Type": "multipart/form-data", 23 | }, 24 | onUploadProgress: (progressEvent) => { 25 | setUploadPercentage( 26 | parseInt( 27 | Math.round((progressEvent.loaded * 100) / progressEvent.total) 28 | ) 29 | ); 30 | }, 31 | }) 32 | .then((response) => { 33 | console.log(response.data); 34 | handleInput(identifier, response.data.url); 35 | setPopup({ 36 | open: true, 37 | severity: "success", 38 | message: response.data.message, 39 | }); 40 | }) 41 | .catch((err) => { 42 | console.log(err.response); 43 | setPopup({ 44 | open: true, 45 | severity: "error", 46 | message: err.response.statusText, 47 | // message: err.response.data 48 | // ? err.response.data.message 49 | // : err.response.statusText, 50 | }); 51 | }); 52 | }; 53 | 54 | return ( 55 | 56 | 57 | 58 | 80 | 81 | 82 | 91 | 92 | 93 | 102 | 103 | 104 | {uploadPercentage !== 0 ? ( 105 | 106 | 107 | 108 | ) : null} 109 | 110 | ); 111 | }; 112 | 113 | export default FileUploadInput; 114 | -------------------------------------------------------------------------------- /frontend/src/lib/MessagePopup.js: -------------------------------------------------------------------------------- 1 | import { Snackbar, Slide } from "@material-ui/core"; 2 | import { Alert } from "@material-ui/lab"; 3 | 4 | const MessagePopup = (props) => { 5 | const handleClose = (event, reason) => { 6 | if (reason === "clickaway") { 7 | return; 8 | } 9 | props.setOpen(false); 10 | }; 11 | return ( 12 | 13 | 14 | {props.message} 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default MessagePopup; 21 | -------------------------------------------------------------------------------- /frontend/src/lib/PasswordInput.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | FormControl, 4 | InputLabel, 5 | OutlinedInput, 6 | InputAdornment, 7 | IconButton, 8 | FormHelperText, 9 | } from "@material-ui/core"; 10 | import Visibility from "@material-ui/icons/Visibility"; 11 | import VisibilityOff from "@material-ui/icons/VisibilityOff"; 12 | 13 | const PasswordInput = (props) => { 14 | const [showPassword, setShowPassword] = useState(false); 15 | 16 | const handleShowPassword = () => { 17 | setShowPassword(!showPassword); 18 | }; 19 | 20 | const handleMouseDownPassword = (event) => { 21 | event.preventDefault(); 22 | }; 23 | 24 | return ( 25 | <> 26 | 27 | 28 | {props.label} 29 | 30 | 35 | 40 | {showPassword ? : } 41 | 42 | 43 | } 44 | value={props.value} 45 | onChange={(event) => props.onChange(event)} 46 | labelWidth={props.labelWidth ? props.labelWidth : 70} 47 | className={props.className} 48 | onBlur={props.onBlur ? props.onBlur : null} 49 | /> 50 | {props.helperText ? ( 51 | {props.helperText} 52 | ) : null} 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default PasswordInput; 59 | -------------------------------------------------------------------------------- /frontend/src/lib/apiList.js: -------------------------------------------------------------------------------- 1 | export const server = "http://localhost:4444"; 2 | 3 | const apiList = { 4 | login: `${server}/auth/login`, 5 | signup: `${server}/auth/signup`, 6 | uploadResume: `${server}/upload/resume`, 7 | uploadProfileImage: `${server}/upload/profile`, 8 | jobs: `${server}/api/jobs`, 9 | applications: `${server}/api/applications`, 10 | rating: `${server}/api/rating`, 11 | user: `${server}/api/user`, 12 | applicants: `${server}/api/applicants`, 13 | }; 14 | 15 | export default apiList; 16 | -------------------------------------------------------------------------------- /frontend/src/lib/isAuth.js: -------------------------------------------------------------------------------- 1 | const isAuth = () => { 2 | return localStorage.getItem("token"); 3 | }; 4 | 5 | export const userType = () => { 6 | return localStorage.getItem("type"); 7 | }; 8 | 9 | export default isAuth; 10 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | --------------------------------------------------------------------------------