Repository: b30wulffz/job-portal
Branch: main
Commit: 208edaa77d51
Files: 50
Total size: 223.5 KB
Directory structure:
gitextract_gqjuut0s/
├── .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.json
│ ├── routes/
│ │ ├── apiRoutes.js
│ │ ├── authRoutes.js
│ │ ├── downloadRoutes.js
│ │ └── uploadRoutes.js
│ └── server.js
├── dummyData
└── frontend/
├── .gitignore
├── README.md
├── package.json
├── public/
│ ├── index.html
│ ├── 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
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
© 2021 GitHub, Inc.
Terms
Privacy
Security
Status
Help
Contact GitHub
Pricing
API
Training
Blog
About
Octotree
Login with GitHub
backend/public/profile/*
backend/public/resume/*
================================================
FILE: README.md
================================================
# Job Portal
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.
Demo: [Click Here](https://www.youtube.com/watch?v=lIrN-LbbBnw&ab_channel=ShlokPandey)
Directory structure of the web app is as follows:
```
- backend/
- public/
- profile/
- resume/
- frontend/
- README.md
```
## Instructions for initializing web app:
- Install Node JS, MongoDB in the machine.
- Start MongoDB server: `sudo service mongod start`
- Move inside backend directory: `cd backend`
- Install dependencies in backend directory: `npm install`
- Start express server: `npm start`
- Backend server will start on port 4444.
- Now go inside frontend directory: `cd ..\frontend`
- Install dependencies in frontend directory: `npm install`
- Start web app's frontend server: `npm start`
- Frontend server will start on port 3000.
- Now open `http://localhost:3000/` and proceed creating jobs and applications by signing up in required categories.
## Dependencies:
- Frontend
- @material-ui/core
- @material-ui/icons
- @material-ui/lab
- axios
- material-ui-chip-input
- react-phone-input-2
- Backend
- bcrypt
- body-parser
- connect-flash
- connect-mongo
- cors
- crypto
- express
- express-session
- jsonwebtoken
- mongoose
- mongoose-type-email
- multer
- passport
- passport-jwt
- passport-local
- uuid
# Machine Specifications
Details of the machine on which the webapp was tested:
- Operating System: Elementary OS 5.1 (Hera)
- Terminal: Bash
- Processor: Intel Core i7-8750H CPU @ 2.20 GHz 2.21 GHz
- RAM: 16 GB
================================================
FILE: backend/db/Application.js
================================================
const mongoose = require("mongoose");
let schema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
recruiterId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
jobId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
status: {
type: String,
enum: [
"applied", // when a applicant is applied
"shortlisted", // when a applicant is shortlisted
"accepted", // when a applicant is accepted
"rejected", // when a applicant is rejected
"deleted", // when any job is deleted
"cancelled", // an application is cancelled by its author or when other application is accepted
"finished", // when job is over
],
default: "applied",
required: true,
},
dateOfApplication: {
type: Date,
default: Date.now,
},
dateOfJoining: {
type: Date,
validate: [
{
validator: function (value) {
return this.dateOfApplication <= value;
},
msg: "dateOfJoining should be greater than dateOfApplication",
},
],
},
sop: {
type: String,
validate: {
validator: function (v) {
return v.split(" ").filter((ele) => ele != "").length <= 250;
},
msg: "Statement of purpose should not be greater than 250 words",
},
},
},
{ collation: { locale: "en" } }
);
// schema.virtual("applicationUser", {
// ref: "JobApplicantInfo",
// localField: "userId",
// foreignField: "userId",
// justOne: true,
// });
// schema.virtual("applicationRecruiter", {
// ref: "RecruiterInfo",
// localField: "recruiterId",
// foreignField: "userId",
// justOne: true,
// });
// schema.virtual("applicationJob", {
// ref: "jobs",
// localField: "jobId",
// foreignField: "_id",
// justOne: true,
// });
module.exports = mongoose.model("applications", schema);
================================================
FILE: backend/db/Job.js
================================================
const mongoose = require("mongoose");
let schema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
title: {
type: String,
required: true,
},
maxApplicants: {
type: Number,
validate: [
{
validator: Number.isInteger,
msg: "maxApplicants should be an integer",
},
{
validator: function (value) {
return value > 0;
},
msg: "maxApplicants should greater than 0",
},
],
},
maxPositions: {
type: Number,
validate: [
{
validator: Number.isInteger,
msg: "maxPostions should be an integer",
},
{
validator: function (value) {
return value > 0;
},
msg: "maxPositions should greater than 0",
},
],
},
activeApplications: {
type: Number,
default: 0,
validate: [
{
validator: Number.isInteger,
msg: "activeApplications should be an integer",
},
{
validator: function (value) {
return value >= 0;
},
msg: "activeApplications should greater than equal to 0",
},
],
},
acceptedCandidates: {
type: Number,
default: 0,
validate: [
{
validator: Number.isInteger,
msg: "acceptedCandidates should be an integer",
},
{
validator: function (value) {
return value >= 0;
},
msg: "acceptedCandidates should greater than equal to 0",
},
],
},
dateOfPosting: {
type: Date,
default: Date.now,
},
deadline: {
type: Date,
validate: [
{
validator: function (value) {
return this.dateOfPosting < value;
},
msg: "deadline should be greater than dateOfPosting",
},
],
},
skillsets: [String],
jobType: {
type: String,
required: true,
},
duration: {
type: Number,
min: 0,
validate: [
{
validator: Number.isInteger,
msg: "Duration should be an integer",
},
],
},
salary: {
type: Number,
validate: [
{
validator: Number.isInteger,
msg: "Salary should be an integer",
},
{
validator: function (value) {
return value >= 0;
},
msg: "Salary should be positive",
},
],
},
rating: {
type: Number,
max: 5.0,
default: -1.0,
validate: {
validator: function (v) {
return v >= -1.0 && v <= 5.0;
},
msg: "Invalid rating",
},
},
},
{ collation: { locale: "en" } }
);
module.exports = mongoose.model("jobs", schema);
================================================
FILE: backend/db/JobApplicant.js
================================================
const mongoose = require("mongoose");
let schema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
name: {
type: String,
required: true,
},
education: [
{
institutionName: {
type: String,
required: true,
},
startYear: {
type: Number,
min: 1930,
max: new Date().getFullYear(),
required: true,
validate: Number.isInteger,
},
endYear: {
type: Number,
max: new Date().getFullYear(),
validate: [
{ validator: Number.isInteger, msg: "Year should be an integer" },
{
validator: function (value) {
return this.startYear <= value;
},
msg: "End year should be greater than or equal to Start year",
},
],
},
},
],
skills: [String],
rating: {
type: Number,
max: 5.0,
default: -1.0,
validate: {
validator: function (v) {
return v >= -1.0 && v <= 5.0;
},
msg: "Invalid rating",
},
},
resume: {
type: String,
},
profile: {
type: String,
},
},
{ collation: { locale: "en" } }
);
module.exports = mongoose.model("JobApplicantInfo", schema);
================================================
FILE: backend/db/Rating.js
================================================
const mongoose = require("mongoose");
let schema = new mongoose.Schema(
{
category: {
type: String,
enum: ["job", "applicant"],
required: true,
},
receiverId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
senderId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
rating: {
type: Number,
max: 5.0,
default: -1.0,
validate: {
validator: function (v) {
return v >= -1.0 && v <= 5.0;
},
msg: "Invalid rating",
},
},
},
{ collation: { locale: "en" } }
);
schema.index({ category: 1, receiverId: 1, senderId: 1 }, { unique: true });
module.exports = mongoose.model("ratings", schema);
================================================
FILE: backend/db/Recruiter.js
================================================
const mongoose = require("mongoose");
let schema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
name: {
type: String,
required: true,
},
contactNumber: {
type: String,
validate: {
validator: function (v) {
return v !== "" ? /\+\d{1,3}\d{10}/.test(v) : true;
},
msg: "Phone number is invalid!",
},
},
bio: {
type: String,
},
},
{ collation: { locale: "en" } }
);
module.exports = mongoose.model("RecruiterInfo", schema);
================================================
FILE: backend/db/User.js
================================================
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
require("mongoose-type-email");
let schema = new mongoose.Schema(
{
email: {
type: mongoose.SchemaTypes.Email,
unique: true,
lowercase: true,
required: true,
},
password: {
type: String,
required: true,
},
type: {
type: String,
enum: ["recruiter", "applicant"],
required: true,
},
},
{ collation: { locale: "en" } }
);
// Password hashing
schema.pre("save", function (next) {
let user = this;
// if the data is not modified
if (!user.isModified("password")) {
return next();
}
bcrypt.hash(user.password, 10, (err, hash) => {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
// Password verification upon login
schema.methods.login = function (password) {
let user = this;
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, result) => {
if (err) {
reject(err);
}
if (result) {
resolve();
} else {
reject();
}
});
});
};
module.exports = mongoose.model("UserAuth", schema);
================================================
FILE: backend/lib/authKeys.js
================================================
module.exports = {
jwtSecretKey: "jwt_secret",
};
================================================
FILE: backend/lib/jwtAuth.js
================================================
const passport = require("passport");
const jwtAuth = (req, res, next) => {
passport.authenticate("jwt", { session: false }, function (err, user, info) {
if (err) {
return next(err);
}
if (!user) {
res.status(401).json(info);
return;
}
req.user = user;
next();
})(req, res, next);
};
module.exports = jwtAuth;
================================================
FILE: backend/lib/passportConfig.js
================================================
const passport = require("passport");
const Strategy = require("passport-local").Strategy;
const passportJWT = require("passport-jwt");
const JWTStrategy = passportJWT.Strategy;
const ExtractJWT = passportJWT.ExtractJwt;
const User = require("../db/User");
const authKeys = require("./authKeys");
const filterJson = (obj, unwantedKeys) => {
const filteredObj = {};
Object.keys(obj).forEach((key) => {
if (unwantedKeys.indexOf(key) === -1) {
filteredObj[key] = obj[key];
}
});
return filteredObj;
};
passport.use(
new Strategy(
{
usernameField: "email",
passReqToCallback: true,
},
(req, email, password, done, res) => {
// console.log(email, password);
User.findOne({ email: email }, (err, user) => {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: "User does not exist",
});
}
user
.login(password)
.then(() => {
// let userSecure = {};
// const unwantedKeys = ["password", "__v"];
// Object.keys(user["_doc"]).forEach((key) => {
// if (unwantedKeys.indexOf(key) === -1) {
// userSecure[key] = user[key];
// }
// });
user["_doc"] = filterJson(user["_doc"], ["password", "__v"]);
return done(null, user);
})
.catch((err) => {
return done(err, false, {
message: "Password is incorrect.",
});
});
});
}
)
);
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: authKeys.jwtSecretKey,
},
(jwt_payload, done) => {
User.findById(jwt_payload._id)
.then((user) => {
console.log(Object.keys(jwt_payload));
if (!user) {
return done(null, false, {
message: "JWT Token does not exist",
});
}
user["_doc"] = filterJson(user["_doc"], ["password", "__v"]);
return done(null, user);
})
.catch((err) => {
return done(err, false, {
message: "Incorrect Token",
});
});
}
)
);
module.exports = passport;
================================================
FILE: backend/package.json
================================================
{
"name": "job-portal-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx nodemon server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",
"connect-flash": "^0.1.1",
"connect-mongo": "^3.2.0",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"express": "^4.17.1",
"express-session": "^1.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.11.11",
"mongoose-type-email": "^1.1.2",
"multer": "^2.0.0-rc.2",
"passport": "^0.4.1",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"uuid": "^8.3.2"
}
}
================================================
FILE: backend/routes/apiRoutes.js
================================================
const express = require("express");
const mongoose = require("mongoose");
const jwtAuth = require("../lib/jwtAuth");
const User = require("../db/User");
const JobApplicant = require("../db/JobApplicant");
const Recruiter = require("../db/Recruiter");
const Job = require("../db/Job");
const Application = require("../db/Application");
const Rating = require("../db/Rating");
const router = express.Router();
// To add new job
router.post("/jobs", jwtAuth, (req, res) => {
const user = req.user;
if (user.type != "recruiter") {
res.status(401).json({
message: "You don't have permissions to add jobs",
});
return;
}
const data = req.body;
let job = new Job({
userId: user._id,
title: data.title,
maxApplicants: data.maxApplicants,
maxPositions: data.maxPositions,
dateOfPosting: data.dateOfPosting,
deadline: data.deadline,
skillsets: data.skillsets,
jobType: data.jobType,
duration: data.duration,
salary: data.salary,
rating: data.rating,
});
job
.save()
.then(() => {
res.json({ message: "Job added successfully to the database" });
})
.catch((err) => {
res.status(400).json(err);
});
});
// to get all the jobs [pagination] [for recruiter personal and for everyone]
router.get("/jobs", jwtAuth, (req, res) => {
let user = req.user;
let findParams = {};
let sortParams = {};
// const page = parseInt(req.query.page) ? parseInt(req.query.page) : 1;
// const limit = parseInt(req.query.limit) ? parseInt(req.query.limit) : 10;
// const skip = page - 1 >= 0 ? (page - 1) * limit : 0;
// to list down jobs posted by a particular recruiter
if (user.type === "recruiter" && req.query.myjobs) {
findParams = {
...findParams,
userId: user._id,
};
}
if (req.query.q) {
findParams = {
...findParams,
title: {
$regex: new RegExp(req.query.q, "i"),
},
};
}
if (req.query.jobType) {
let jobTypes = [];
if (Array.isArray(req.query.jobType)) {
jobTypes = req.query.jobType;
} else {
jobTypes = [req.query.jobType];
}
console.log(jobTypes);
findParams = {
...findParams,
jobType: {
$in: jobTypes,
},
};
}
if (req.query.salaryMin && req.query.salaryMax) {
findParams = {
...findParams,
$and: [
{
salary: {
$gte: parseInt(req.query.salaryMin),
},
},
{
salary: {
$lte: parseInt(req.query.salaryMax),
},
},
],
};
} else if (req.query.salaryMin) {
findParams = {
...findParams,
salary: {
$gte: parseInt(req.query.salaryMin),
},
};
} else if (req.query.salaryMax) {
findParams = {
...findParams,
salary: {
$lte: parseInt(req.query.salaryMax),
},
};
}
if (req.query.duration) {
findParams = {
...findParams,
duration: {
$lt: parseInt(req.query.duration),
},
};
}
if (req.query.asc) {
if (Array.isArray(req.query.asc)) {
req.query.asc.map((key) => {
sortParams = {
...sortParams,
[key]: 1,
};
});
} else {
sortParams = {
...sortParams,
[req.query.asc]: 1,
};
}
}
if (req.query.desc) {
if (Array.isArray(req.query.desc)) {
req.query.desc.map((key) => {
sortParams = {
...sortParams,
[key]: -1,
};
});
} else {
sortParams = {
...sortParams,
[req.query.desc]: -1,
};
}
}
console.log(findParams);
console.log(sortParams);
// Job.find(findParams).collation({ locale: "en" }).sort(sortParams);
// .skip(skip)
// .limit(limit)
let arr = [
{
$lookup: {
from: "recruiterinfos",
localField: "userId",
foreignField: "userId",
as: "recruiter",
},
},
{ $unwind: "$recruiter" },
{ $match: findParams },
];
if (Object.keys(sortParams).length > 0) {
arr = [
{
$lookup: {
from: "recruiterinfos",
localField: "userId",
foreignField: "userId",
as: "recruiter",
},
},
{ $unwind: "$recruiter" },
{ $match: findParams },
{
$sort: sortParams,
},
];
}
console.log(arr);
Job.aggregate(arr)
.then((posts) => {
if (posts == null) {
res.status(404).json({
message: "No job found",
});
return;
}
res.json(posts);
})
.catch((err) => {
res.status(400).json(err);
});
});
// to get info about a particular job
router.get("/jobs/:id", jwtAuth, (req, res) => {
Job.findOne({ _id: req.params.id })
.then((job) => {
if (job == null) {
res.status(400).json({
message: "Job does not exist",
});
return;
}
res.json(job);
})
.catch((err) => {
res.status(400).json(err);
});
});
// to update info of a particular job
router.put("/jobs/:id", jwtAuth, (req, res) => {
const user = req.user;
if (user.type != "recruiter") {
res.status(401).json({
message: "You don't have permissions to change the job details",
});
return;
}
Job.findOne({
_id: req.params.id,
userId: user.id,
})
.then((job) => {
if (job == null) {
res.status(404).json({
message: "Job does not exist",
});
return;
}
const data = req.body;
if (data.maxApplicants) {
job.maxApplicants = data.maxApplicants;
}
if (data.maxPositions) {
job.maxPositions = data.maxPositions;
}
if (data.deadline) {
job.deadline = data.deadline;
}
job
.save()
.then(() => {
res.json({
message: "Job details updated successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
});
// to delete a job
router.delete("/jobs/:id", jwtAuth, (req, res) => {
const user = req.user;
if (user.type != "recruiter") {
res.status(401).json({
message: "You don't have permissions to delete the job",
});
return;
}
Job.findOneAndDelete({
_id: req.params.id,
userId: user.id,
})
.then((job) => {
if (job === null) {
res.status(401).json({
message: "You don't have permissions to delete the job",
});
return;
}
res.json({
message: "Job deleted successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
});
// get user's personal details
router.get("/user", jwtAuth, (req, res) => {
const user = req.user;
if (user.type === "recruiter") {
Recruiter.findOne({ userId: user._id })
.then((recruiter) => {
if (recruiter == null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
res.json(recruiter);
})
.catch((err) => {
res.status(400).json(err);
});
} else {
JobApplicant.findOne({ userId: user._id })
.then((jobApplicant) => {
if (jobApplicant == null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
res.json(jobApplicant);
})
.catch((err) => {
res.status(400).json(err);
});
}
});
// get user details from id
router.get("/user/:id", jwtAuth, (req, res) => {
User.findOne({ _id: req.params.id })
.then((userData) => {
if (userData === null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
if (userData.type === "recruiter") {
Recruiter.findOne({ userId: userData._id })
.then((recruiter) => {
if (recruiter === null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
res.json(recruiter);
})
.catch((err) => {
res.status(400).json(err);
});
} else {
JobApplicant.findOne({ userId: userData._id })
.then((jobApplicant) => {
if (jobApplicant === null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
res.json(jobApplicant);
})
.catch((err) => {
res.status(400).json(err);
});
}
})
.catch((err) => {
res.status(400).json(err);
});
});
// update user details
router.put("/user", jwtAuth, (req, res) => {
const user = req.user;
const data = req.body;
if (user.type == "recruiter") {
Recruiter.findOne({ userId: user._id })
.then((recruiter) => {
if (recruiter == null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
if (data.name) {
recruiter.name = data.name;
}
if (data.contactNumber) {
recruiter.contactNumber = data.contactNumber;
}
if (data.bio) {
recruiter.bio = data.bio;
}
recruiter
.save()
.then(() => {
res.json({
message: "User information updated successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
JobApplicant.findOne({ userId: user._id })
.then((jobApplicant) => {
if (jobApplicant == null) {
res.status(404).json({
message: "User does not exist",
});
return;
}
if (data.name) {
jobApplicant.name = data.name;
}
if (data.education) {
jobApplicant.education = data.education;
}
if (data.skills) {
jobApplicant.skills = data.skills;
}
if (data.resume) {
jobApplicant.resume = data.resume;
}
if (data.profile) {
jobApplicant.profile = data.profile;
}
console.log(jobApplicant);
jobApplicant
.save()
.then(() => {
res.json({
message: "User information updated successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
}
});
// apply for a job [todo: test: done]
router.post("/jobs/:id/applications", jwtAuth, (req, res) => {
const user = req.user;
if (user.type != "applicant") {
res.status(401).json({
message: "You don't have permissions to apply for a job",
});
return;
}
const data = req.body;
const jobId = req.params.id;
// check whether applied previously
// find job
// check count of active applications < limit
// check user had < 10 active applications && check if user is not having any accepted jobs (user id)
// store the data in applications
Application.findOne({
userId: user._id,
jobId: jobId,
status: {
$nin: ["deleted", "accepted", "cancelled"],
},
})
.then((appliedApplication) => {
console.log(appliedApplication);
if (appliedApplication !== null) {
res.status(400).json({
message: "You have already applied for this job",
});
return;
}
Job.findOne({ _id: jobId })
.then((job) => {
if (job === null) {
res.status(404).json({
message: "Job does not exist",
});
return;
}
Application.countDocuments({
jobId: jobId,
status: {
$nin: ["rejected", "deleted", "cancelled", "finished"],
},
})
.then((activeApplicationCount) => {
if (activeApplicationCount < job.maxApplicants) {
Application.countDocuments({
userId: user._id,
status: {
$nin: ["rejected", "deleted", "cancelled", "finished"],
},
})
.then((myActiveApplicationCount) => {
if (myActiveApplicationCount < 10) {
Application.countDocuments({
userId: user._id,
status: "accepted",
}).then((acceptedJobs) => {
if (acceptedJobs === 0) {
const application = new Application({
userId: user._id,
recruiterId: job.userId,
jobId: job._id,
status: "applied",
sop: data.sop,
});
application
.save()
.then(() => {
res.json({
message: "Job application successful",
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.status(400).json({
message:
"You already have an accepted job. Hence you cannot apply.",
});
}
});
} else {
res.status(400).json({
message:
"You have 10 active applications. Hence you cannot apply.",
});
}
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.status(400).json({
message: "Application limit reached",
});
}
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.json(400).json(err);
});
});
// recruiter gets applications for a particular job [pagination] [todo: test: done]
router.get("/jobs/:id/applications", jwtAuth, (req, res) => {
const user = req.user;
if (user.type != "recruiter") {
res.status(401).json({
message: "You don't have permissions to view job applications",
});
return;
}
const jobId = req.params.id;
// const page = parseInt(req.query.page) ? parseInt(req.query.page) : 1;
// const limit = parseInt(req.query.limit) ? parseInt(req.query.limit) : 10;
// const skip = page - 1 >= 0 ? (page - 1) * limit : 0;
let findParams = {
jobId: jobId,
recruiterId: user._id,
};
let sortParams = {};
if (req.query.status) {
findParams = {
...findParams,
status: req.query.status,
};
}
Application.find(findParams)
.collation({ locale: "en" })
.sort(sortParams)
// .skip(skip)
// .limit(limit)
.then((applications) => {
res.json(applications);
})
.catch((err) => {
res.status(400).json(err);
});
});
// recruiter/applicant gets all his applications [pagination]
router.get("/applications", jwtAuth, (req, res) => {
const user = req.user;
// const page = parseInt(req.query.page) ? parseInt(req.query.page) : 1;
// const limit = parseInt(req.query.limit) ? parseInt(req.query.limit) : 10;
// const skip = page - 1 >= 0 ? (page - 1) * limit : 0;
Application.aggregate([
{
$lookup: {
from: "jobapplicantinfos",
localField: "userId",
foreignField: "userId",
as: "jobApplicant",
},
},
{ $unwind: "$jobApplicant" },
{
$lookup: {
from: "jobs",
localField: "jobId",
foreignField: "_id",
as: "job",
},
},
{ $unwind: "$job" },
{
$lookup: {
from: "recruiterinfos",
localField: "recruiterId",
foreignField: "userId",
as: "recruiter",
},
},
{ $unwind: "$recruiter" },
{
$match: {
[user.type === "recruiter" ? "recruiterId" : "userId"]: user._id,
},
},
{
$sort: {
dateOfApplication: -1,
},
},
])
.then((applications) => {
res.json(applications);
})
.catch((err) => {
res.status(400).json(err);
});
});
// update status of application: [Applicant: Can cancel, Recruiter: Can do everything] [todo: test: done]
router.put("/applications/:id", jwtAuth, (req, res) => {
const user = req.user;
const id = req.params.id;
const status = req.body.status;
// "applied", // when a applicant is applied
// "shortlisted", // when a applicant is shortlisted
// "accepted", // when a applicant is accepted
// "rejected", // when a applicant is rejected
// "deleted", // when any job is deleted
// "cancelled", // an application is cancelled by its author or when other application is accepted
// "finished", // when job is over
if (user.type === "recruiter") {
if (status === "accepted") {
// get job id from application
// get job info for maxPositions count
// count applications that are already accepted
// compare and if condition is satisfied, then save
Application.findOne({
_id: id,
recruiterId: user._id,
})
.then((application) => {
if (application === null) {
res.status(404).json({
message: "Application not found",
});
return;
}
Job.findOne({
_id: application.jobId,
userId: user._id,
}).then((job) => {
if (job === null) {
res.status(404).json({
message: "Job does not exist",
});
return;
}
Application.countDocuments({
recruiterId: user._id,
jobId: job._id,
status: "accepted",
}).then((activeApplicationCount) => {
if (activeApplicationCount < job.maxPositions) {
// accepted
application.status = status;
application.dateOfJoining = req.body.dateOfJoining;
application
.save()
.then(() => {
Application.updateMany(
{
_id: {
$ne: application._id,
},
userId: application.userId,
status: {
$nin: [
"rejected",
"deleted",
"cancelled",
"accepted",
"finished",
],
},
},
{
$set: {
status: "cancelled",
},
},
{ multi: true }
)
.then(() => {
if (status === "accepted") {
Job.findOneAndUpdate(
{
_id: job._id,
userId: user._id,
},
{
$set: {
acceptedCandidates: activeApplicationCount + 1,
},
}
)
.then(() => {
res.json({
message: `Application ${status} successfully`,
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.json({
message: `Application ${status} successfully`,
});
}
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.status(400).json({
message: "All positions for this job are already filled",
});
}
});
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
Application.findOneAndUpdate(
{
_id: id,
recruiterId: user._id,
status: {
$nin: ["rejected", "deleted", "cancelled"],
},
},
{
$set: {
status: status,
},
}
)
.then((application) => {
if (application === null) {
res.status(400).json({
message: "Application status cannot be updated",
});
return;
}
if (status === "finished") {
res.json({
message: `Job ${status} successfully`,
});
} else {
res.json({
message: `Application ${status} successfully`,
});
}
})
.catch((err) => {
res.status(400).json(err);
});
}
} else {
if (status === "cancelled") {
console.log(id);
console.log(user._id);
Application.findOneAndUpdate(
{
_id: id,
userId: user._id,
},
{
$set: {
status: status,
},
}
)
.then((tmp) => {
console.log(tmp);
res.json({
message: `Application ${status} successfully`,
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.status(401).json({
message: "You don't have permissions to update job status",
});
}
}
});
// get a list of final applicants for current job : recruiter
// get a list of final applicants for all his jobs : recuiter
router.get("/applicants", jwtAuth, (req, res) => {
const user = req.user;
if (user.type === "recruiter") {
let findParams = {
recruiterId: user._id,
};
if (req.query.jobId) {
findParams = {
...findParams,
jobId: new mongoose.Types.ObjectId(req.query.jobId),
};
}
if (req.query.status) {
if (Array.isArray(req.query.status)) {
findParams = {
...findParams,
status: { $in: req.query.status },
};
} else {
findParams = {
...findParams,
status: req.query.status,
};
}
}
let sortParams = {};
if (!req.query.asc && !req.query.desc) {
sortParams = { _id: 1 };
}
if (req.query.asc) {
if (Array.isArray(req.query.asc)) {
req.query.asc.map((key) => {
sortParams = {
...sortParams,
[key]: 1,
};
});
} else {
sortParams = {
...sortParams,
[req.query.asc]: 1,
};
}
}
if (req.query.desc) {
if (Array.isArray(req.query.desc)) {
req.query.desc.map((key) => {
sortParams = {
...sortParams,
[key]: -1,
};
});
} else {
sortParams = {
...sortParams,
[req.query.desc]: -1,
};
}
}
Application.aggregate([
{
$lookup: {
from: "jobapplicantinfos",
localField: "userId",
foreignField: "userId",
as: "jobApplicant",
},
},
{ $unwind: "$jobApplicant" },
{
$lookup: {
from: "jobs",
localField: "jobId",
foreignField: "_id",
as: "job",
},
},
{ $unwind: "$job" },
{ $match: findParams },
{ $sort: sortParams },
])
.then((applications) => {
if (applications.length === 0) {
res.status(404).json({
message: "No applicants found",
});
return;
}
res.json(applications);
})
.catch((err) => {
res.status(400).json(err);
});
} else {
res.status(400).json({
message: "You are not allowed to access applicants list",
});
}
});
// to add or update a rating [todo: test]
router.put("/rating", jwtAuth, (req, res) => {
const user = req.user;
const data = req.body;
if (user.type === "recruiter") {
// can rate applicant
Rating.findOne({
senderId: user._id,
receiverId: data.applicantId,
category: "applicant",
})
.then((rating) => {
if (rating === null) {
console.log("new rating");
Application.countDocuments({
userId: data.applicantId,
recruiterId: user._id,
status: {
$in: ["accepted", "finished"],
},
})
.then((acceptedApplicant) => {
if (acceptedApplicant > 0) {
// add a new rating
rating = new Rating({
category: "applicant",
receiverId: data.applicantId,
senderId: user._id,
rating: data.rating,
});
rating
.save()
.then(() => {
// get the average of ratings
Rating.aggregate([
{
$match: {
receiverId: mongoose.Types.ObjectId(data.applicantId),
category: "applicant",
},
},
{
$group: {
_id: {},
average: { $avg: "$rating" },
},
},
])
.then((result) => {
// update the user's rating
if (result === null) {
res.status(400).json({
message: "Error while calculating rating",
});
return;
}
const avg = result[0].average;
JobApplicant.findOneAndUpdate(
{
userId: data.applicantId,
},
{
$set: {
rating: avg,
},
}
)
.then((applicant) => {
if (applicant === null) {
res.status(400).json({
message:
"Error while updating applicant's average rating",
});
return;
}
res.json({
message: "Rating added successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
// you cannot rate
res.status(400).json({
message:
"Applicant didn't worked under you. Hence you cannot give a rating.",
});
}
})
.catch((err) => {
res.status(400).json(err);
});
} else {
rating.rating = data.rating;
rating
.save()
.then(() => {
// get the average of ratings
Rating.aggregate([
{
$match: {
receiverId: mongoose.Types.ObjectId(data.applicantId),
category: "applicant",
},
},
{
$group: {
_id: {},
average: { $avg: "$rating" },
},
},
])
.then((result) => {
// update the user's rating
if (result === null) {
res.status(400).json({
message: "Error while calculating rating",
});
return;
}
const avg = result[0].average;
JobApplicant.findOneAndUpdate(
{
userId: data.applicantId,
},
{
$set: {
rating: avg,
},
}
)
.then((applicant) => {
if (applicant === null) {
res.status(400).json({
message:
"Error while updating applicant's average rating",
});
return;
}
res.json({
message: "Rating updated successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
}
})
.catch((err) => {
res.status(400).json(err);
});
} else {
// applicant can rate job
Rating.findOne({
senderId: user._id,
receiverId: data.jobId,
category: "job",
})
.then((rating) => {
console.log(user._id);
console.log(data.jobId);
console.log(rating);
if (rating === null) {
console.log(rating);
Application.countDocuments({
userId: user._id,
jobId: data.jobId,
status: {
$in: ["accepted", "finished"],
},
})
.then((acceptedApplicant) => {
if (acceptedApplicant > 0) {
// add a new rating
rating = new Rating({
category: "job",
receiverId: data.jobId,
senderId: user._id,
rating: data.rating,
});
rating
.save()
.then(() => {
// get the average of ratings
Rating.aggregate([
{
$match: {
receiverId: mongoose.Types.ObjectId(data.jobId),
category: "job",
},
},
{
$group: {
_id: {},
average: { $avg: "$rating" },
},
},
])
.then((result) => {
if (result === null) {
res.status(400).json({
message: "Error while calculating rating",
});
return;
}
const avg = result[0].average;
Job.findOneAndUpdate(
{
_id: data.jobId,
},
{
$set: {
rating: avg,
},
}
)
.then((foundJob) => {
if (foundJob === null) {
res.status(400).json({
message:
"Error while updating job's average rating",
});
return;
}
res.json({
message: "Rating added successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
} else {
// you cannot rate
res.status(400).json({
message:
"You haven't worked for this job. Hence you cannot give a rating.",
});
}
})
.catch((err) => {
res.status(400).json(err);
});
} else {
// update the rating
rating.rating = data.rating;
rating
.save()
.then(() => {
// get the average of ratings
Rating.aggregate([
{
$match: {
receiverId: mongoose.Types.ObjectId(data.jobId),
category: "job",
},
},
{
$group: {
_id: {},
average: { $avg: "$rating" },
},
},
])
.then((result) => {
if (result === null) {
res.status(400).json({
message: "Error while calculating rating",
});
return;
}
const avg = result[0].average;
console.log(avg);
Job.findOneAndUpdate(
{
_id: data.jobId,
},
{
$set: {
rating: avg,
},
}
)
.then((foundJob) => {
if (foundJob === null) {
res.status(400).json({
message: "Error while updating job's average rating",
});
return;
}
res.json({
message: "Rating added successfully",
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
})
.catch((err) => {
res.status(400).json(err);
});
}
})
.catch((err) => {
res.status(400).json(err);
});
}
});
// get personal rating
router.get("/rating", jwtAuth, (req, res) => {
const user = req.user;
Rating.findOne({
senderId: user._id,
receiverId: req.query.id,
category: user.type === "recruiter" ? "applicant" : "job",
}).then((rating) => {
if (rating === null) {
res.json({
rating: -1,
});
return;
}
res.json({
rating: rating.rating,
});
});
});
// Application.findOne({
// _id: id,
// userId: user._id,
// })
// .then((application) => {
// application.status = status;
// application
// .save()
// .then(() => {
// res.json({
// message: `Application ${status} successfully`,
// });
// })
// .catch((err) => {
// res.status(400).json(err);
// });
// })
// .catch((err) => {
// res.status(400).json(err);
// });
// router.get("/jobs", (req, res, next) => {
// passport.authenticate("jwt", { session: false }, function (err, user, info) {
// if (err) {
// return next(err);
// }
// if (!user) {
// res.status(401).json(info);
// return;
// }
// })(req, res, next);
// });
module.exports = router;
================================================
FILE: backend/routes/authRoutes.js
================================================
const express = require("express");
const passport = require("passport");
const jwt = require("jsonwebtoken");
const authKeys = require("../lib/authKeys");
const User = require("../db/User");
const JobApplicant = require("../db/JobApplicant");
const Recruiter = require("../db/Recruiter");
const router = express.Router();
router.post("/signup", (req, res) => {
const data = req.body;
let user = new User({
email: data.email,
password: data.password,
type: data.type,
});
user
.save()
.then(() => {
const userDetails =
user.type == "recruiter"
? new Recruiter({
userId: user._id,
name: data.name,
contactNumber: data.contactNumber,
bio: data.bio,
})
: new JobApplicant({
userId: user._id,
name: data.name,
education: data.education,
skills: data.skills,
rating: data.rating,
resume: data.resume,
profile: data.profile,
});
userDetails
.save()
.then(() => {
// Token
const token = jwt.sign({ _id: user._id }, authKeys.jwtSecretKey);
res.json({
token: token,
type: user.type,
});
})
.catch((err) => {
user
.delete()
.then(() => {
res.status(400).json(err);
})
.catch((err) => {
res.json({ error: err });
});
err;
});
})
.catch((err) => {
res.status(400).json(err);
});
});
router.post("/login", (req, res, next) => {
passport.authenticate(
"local",
{ session: false },
function (err, user, info) {
if (err) {
return next(err);
}
if (!user) {
res.status(401).json(info);
return;
}
// Token
const token = jwt.sign({ _id: user._id }, authKeys.jwtSecretKey);
res.json({
token: token,
type: user.type,
});
}
)(req, res, next);
});
module.exports = router;
================================================
FILE: backend/routes/downloadRoutes.js
================================================
const express = require("express");
const fs = require("fs");
const path = require("path");
const router = express.Router();
router.get("/resume/:file", (req, res) => {
const address = path.join(__dirname, `../public/resume/${req.params.file}`);
fs.access(address, fs.F_OK, (err) => {
if (err) {
res.status(404).json({
message: "File not found",
});
return;
}
res.sendFile(address);
});
});
router.get("/profile/:file", (req, res) => {
const address = path.join(__dirname, `../public/profile/${req.params.file}`);
fs.access(address, fs.F_OK, (err) => {
if (err) {
res.status(404).json({
message: "File not found",
});
return;
}
res.sendFile(address);
});
});
module.exports = router;
================================================
FILE: backend/routes/uploadRoutes.js
================================================
const express = require("express");
const multer = require("multer");
const fs = require("fs");
const { v4: uuidv4 } = require("uuid");
const { promisify } = require("util");
const pipeline = promisify(require("stream").pipeline);
const router = express.Router();
const upload = multer();
router.post("/resume", upload.single("file"), (req, res) => {
const { file } = req;
if (file.detectedFileExtension != ".pdf") {
res.status(400).json({
message: "Invalid format",
});
} else {
const filename = `${uuidv4()}${file.detectedFileExtension}`;
pipeline(
file.stream,
fs.createWriteStream(`${__dirname}/../public/resume/${filename}`)
)
.then(() => {
res.send({
message: "File uploaded successfully",
url: `/host/resume/${filename}`,
});
})
.catch((err) => {
res.status(400).json({
message: "Error while uploading",
});
});
}
});
router.post("/profile", upload.single("file"), (req, res) => {
const { file } = req;
if (
file.detectedFileExtension != ".jpg" &&
file.detectedFileExtension != ".png"
) {
res.status(400).json({
message: "Invalid format",
});
} else {
const filename = `${uuidv4()}${file.detectedFileExtension}`;
pipeline(
file.stream,
fs.createWriteStream(`${__dirname}/../public/profile/${filename}`)
)
.then(() => {
res.send({
message: "Profile image uploaded successfully",
url: `/host/profile/${filename}`,
});
})
.catch((err) => {
res.status(400).json({
message: "Error while uploading",
});
});
}
});
module.exports = router;
================================================
FILE: backend/server.js
================================================
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const passportConfig = require("./lib/passportConfig");
const cors = require("cors");
const fs = require("fs");
// MongoDB
mongoose
.connect("mongodb://localhost:27017/jobPortal", {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
})
.then((res) => console.log("Connected to DB"))
.catch((err) => console.log(err));
// initialising directories
if (!fs.existsSync("./public")) {
fs.mkdirSync("./public");
}
if (!fs.existsSync("./public/resume")) {
fs.mkdirSync("./public/resume");
}
if (!fs.existsSync("./public/profile")) {
fs.mkdirSync("./public/profile");
}
const app = express();
const port = 4444;
app.use(bodyParser.json()); // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
// Setting up middlewares
app.use(cors());
app.use(express.json());
app.use(passportConfig.initialize());
// Routing
app.use("/auth", require("./routes/authRoutes"));
app.use("/api", require("./routes/apiRoutes"));
app.use("/upload", require("./routes/uploadRoutes"));
app.use("/host", require("./routes/downloadRoutes"));
app.listen(port, () => {
console.log(`Server started on port ${port}!`);
});
================================================
FILE: dummyData
================================================
POST
localhost:4444/auth/signup
// recruiter
{
"email": "john@gmail.com",
"password": "test",
"type": "recruiter",
"name": "John Smith",
"contactNumber": "+91-7979282839",
"bio": "I am a recruiter from Google Inc"
}
{
"email": "roman@live.in",
"password": "test",
"type": "recruiter",
"name": "Roman Reigns",
"contactNumber": "+91-8879456123",
"bio": "I am a recruiter from Amazon Inc"
}
// applicant
{
"email": "shlok@gmail.com",
"password": "test",
"type": "applicant",
"name": "Shlok Pandey",
"education": [
{
"institutionName": "DPS Azaad Nagar",
"startYear": 2009,
"endYear": 2018
},
{
"institutionName": "IIIT Sri City",
"startYear": 2018,
"endYear": 2020
},
{
"institutionName": "IIIT Hyderabad",
"startYear": 2020
}
],
"skills": [
"JavaScript",
"React JS",
"Django"
]
}
{
"email": "ash@gmail.com",
"password": "test",
"type": "applicant",
"name": "Ashwani Shukla",
"education": [
{
"institutionName": "Allenhouse College",
"startYear": 2019
}
],
"skills": [
"C",
"C++",
"Web Development"
]
}
// Login
POST
localhost:4444/auth/login
{
"email": "john@gmail.com",
"password": "test",
"type": "recruiter"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzMzNWMzNWQ1NTJlMDU5NzA1YzYiLCJpYXQiOjE2MTA5Njk5MDl9.6f4qhjHIaRU_lnY74WB2mK_h7uo32tp1V4uf3DVN5GQ"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NTk5ZTIwODQyZjA1YTE3NzU4ZjkiLCJpYXQiOjE2MTExNTk5NjZ9.DIGyjwezwheqFkex0dZSIOEKrEUXXSNka9Va2B2rYVc"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg0Mzk1ZTUxMDIwYmI2OTVlZGEiLCJpYXQiOjE2MTExNjM3MTV9.6UlyrgwLvM0o8tXcihJVWTnnJNO0WlBXAAF8RKmlgRQ"
}
{
"email": "roman@live.in",
"password": "test",
"type": "recruiter"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzM2N2MzNWQ1NTJlMDU5NzA1YzkiLCJpYXQiOjE2MTA5Njk5NTl9.KVjgEa8U0BcZQ4pfJd1mr2h-CP6ZBOJKanFhmQQFDPo"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NTllNDIwODQyZjA1YTE3NzU4ZmMiLCJpYXQiOjE2MTExNjAwMzZ9.FA0jrx3d1en4zP3ElDkRtUV1pulgPDIpVeXOfBTIy80"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg1OTk1ZTUxMDIwYmI2OTVlZGMiLCJpYXQiOjE2MTExNjM3Mzd9.zLS_VltLBVTXcGqzO4jGjrZ4OagxL2U5MXjcdLn5jNY"
}
{
"email": "shlok@gmail.com",
"password": "test",
"type": "applicant"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzM4YWMzNWQ1NTJlMDU5NzA1Y2IiLCJpYXQiOjE2MTA5Njk5OTR9.8pb8nBN0npAT_uW-2OJV8DuqXDJlPy_HOjm_MdQUwLA"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NWEwMjIwODQyZjA1YTE3NzU4ZmUiLCJpYXQiOjE2MTExNjAwNjZ9.P4NLHyah0O6ISH4LSkEAna7g-YovyM1ArnVGJ54TcEw"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg2Yzk1ZTUxMDIwYmI2OTVlZGUiLCJpYXQiOjE2MTExNjM3NTd9.UuqeMrMGYGJ3tJtvs8Z9VgLxqIF6QUHKjU9EIbQ8Pjc"
}
{
"email": "ash@gmail.com",
"password": "test",
"type": "applicant"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA1NzNhMWMzNWQ1NTJlMDU5NzA1ZDAiLCJpYXQiOjE2MTA5NzAwMTd9.oL617v51AErVlaPGr1oP-5ILQbA9DO2DAoQRFtLh52U"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4NWExNjIwODQyZjA1YTE3NzU5MDMiLCJpYXQiOjE2MTExNjAwODd9.YoYpauFDzF28ZzKkeItbRPdcqw8biNKujNfZCGCKibw"
}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDA4Njg4Mzk1ZTUxMDIwYmI2OTVlZTMiLCJpYXQiOjE2MTExNjM3Nzl9.iFPFXu3aOax6muoCzNJavGSKa9s87S5AIXTiHvJ0OSQ"
}
// jobs
GET
localhost:4444/api/jobs
POST
localhost:4444/api/jobs
Admin 1
{
"title": "Software Development Intern",
"maxApplicants": 5,
"maxPositions": 1,
"deadline": "2021-02-22T18:17:24.519Z",
"skillsets": ["C", "C++", "Javascript"],
"jobType": "Internship",
"duration": 2,
"salary": 4000
}
{
"title": "Web Development(Full Time)",
"maxApplicants": 1,
"maxPositions": 1,
"deadline": "2021-02-20T18:17:24.519Z",
"skillsets": ["PHP", "Django"],
"jobType": "Full Time",
"duration": 0,
"salary": 5000
}
Admin 2
{
"title": "Full Stack Intern",
"maxApplicants": 5,
"maxPositions": 3,
"deadline": "2021-02-20T18:17:24.519Z",
"skillsets": ["ReactJS", "NodeJS", "Express"],
"jobType": "Internship",
"duration": 5,
"salary": 4500
}
// view a job
// delete a job
// update a job
{
"maxApplicants": 4,
"maxPositions": 2,
"deadline": "2021-02-21T18:17:24.519Z"
}
// get user personal details
GET
localhost:4444/api/user
// update user details
PUT
localhost:4444/api/user
{
"name": "Roman Rollins",
"contactNumber": "+91-8945659878",
"bio": "I am a recruiter from Flipkart Inc",
}
// job application
{
"sop": "Shlok: I wouldn't let you down"
}
{
"sop": "Ashwani: I hope you like my resume."
}
// update status
// for client side
{
"status": "cancelled"
}
{
"status": "shortlisted"
}
{
"status": "rejected"
}
{
"status": "accepted"
}
{
"status": "finished"
}
// test rating
{
"applicantId": "6005738ac35d552e059705cb",
"rating": 4.2
}
{
"applicantId": "600573a1c35d552e059705d0",
"rating": 3.5
}
{
"jobId": "600576e16ce7b7325f5b5c70",
"rating": 2.0
}
================================================
FILE: frontend/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: frontend/README.md
================================================
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
================================================
FILE: frontend/package.json
================================================
{
"name": "job-portal",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.3",
"@testing-library/user-event": "^12.6.2",
"axios": "^0.21.1",
"material-ui-chip-input": "^1.1.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-phone-input-2": "^2.13.9",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
================================================
FILE: frontend/public/index.html
================================================
React App
================================================
FILE: frontend/public/manifest.json
================================================
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: frontend/public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
================================================
FILE: frontend/src/App.css
================================================
================================================
FILE: frontend/src/App.js
================================================
import { createContext, useState } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import { Grid, makeStyles } from "@material-ui/core";
import Welcome, { ErrorPage } from "./component/Welcome";
import Navbar from "./component/Navbar";
import Login from "./component/Login";
import Logout from "./component/Logout";
import Signup from "./component/Signup";
import Home from "./component/Home";
import Applications from "./component/Applications";
import Profile from "./component/Profile";
import CreateJobs from "./component/recruiter/CreateJobs";
import MyJobs from "./component/recruiter/MyJobs";
import JobApplications from "./component/recruiter/JobApplications";
import AcceptedApplicants from "./component/recruiter/AcceptedApplicants";
import RecruiterProfile from "./component/recruiter/Profile";
import MessagePopup from "./lib/MessagePopup";
import isAuth, { userType } from "./lib/isAuth";
const useStyles = makeStyles((theme) => ({
body: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
minHeight: "98vh",
paddingTop: "64px",
boxSizing: "border-box",
width: "100%",
},
}));
export const SetPopupContext = createContext();
function App() {
const classes = useStyles();
const [popup, setPopup] = useState({
open: false,
severity: "",
message: "",
});
return (
{userType() === "recruiter" ? (
) : (
)}
setPopup({
...popup,
open: status,
})
}
severity={popup.severity}
message={popup.message}
/>
);
}
export default App;
================================================
FILE: frontend/src/App.test.js
================================================
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render();
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
================================================
FILE: frontend/src/component/Applications.js
================================================
import { useState, useEffect, useContext } from "react";
import {
Button,
Chip,
Grid,
IconButton,
InputAdornment,
makeStyles,
Paper,
TextField,
Typography,
Modal,
Slider,
FormControlLabel,
FormGroup,
MenuItem,
Checkbox,
} from "@material-ui/core";
import Rating from "@material-ui/lab/Rating";
import axios from "axios";
import { SetPopupContext } from "../App";
import apiList from "../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
statusBlock: {
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
textTransform: "uppercase",
},
jobTileOuter: {
padding: "30px",
margin: "20px 0",
boxSizing: "border-box",
width: "100%",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}));
const ApplicationTile = (props) => {
const classes = useStyles();
const { application } = props;
const setPopup = useContext(SetPopupContext);
const [open, setOpen] = useState(false);
const [rating, setRating] = useState(application.job.rating);
const appliedOn = new Date(application.dateOfApplication);
const joinedOn = new Date(application.dateOfJoining);
const fetchRating = () => {
axios
.get(`${apiList.rating}?id=${application.job._id}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setRating(response.data.rating);
console.log(response.data);
})
.catch((err) => {
// console.log(err.response);
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
const changeRating = () => {
axios
.put(
apiList.rating,
{ rating: rating, jobId: application.job._id },
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then((response) => {
console.log(response.data);
setPopup({
open: true,
severity: "success",
message: "Rating updated successfully",
});
fetchRating();
setOpen(false);
})
.catch((err) => {
// console.log(err.response);
console.log(err);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
fetchRating();
setOpen(false);
});
};
const handleClose = () => {
setOpen(false);
};
const colorSet = {
applied: "#3454D1",
shortlisted: "#DC851F",
accepted: "#09BC8A",
rejected: "#D1345B",
deleted: "#B49A67",
cancelled: "#FF8484",
finished: "#4EA5D9",
};
return (
{application.job.title}
Posted By: {application.recruiter.name}
Role : {application.job.jobType}
Salary : ₹ {application.job.salary} per month
Duration :{" "}
{application.job.duration !== 0
? `${application.job.duration} month`
: `Flexible`}
{application.job.skillsets.map((skill) => (
))}
Applied On: {appliedOn.toLocaleDateString()}
{application.status === "accepted" ||
application.status === "finished" ? (
Joined On: {joinedOn.toLocaleDateString()}
) : null}
{application.status}
{application.status === "accepted" ||
application.status === "finished" ? (
) : null}
{
setRating(newValue);
}}
/>
);
};
const Applications = (props) => {
const setPopup = useContext(SetPopupContext);
const [applications, setApplications] = useState([]);
useEffect(() => {
getData();
}, []);
const getData = () => {
axios
.get(apiList.applications, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setApplications(response.data);
})
.catch((err) => {
// console.log(err.response);
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
return (
Applications
{applications.length > 0 ? (
applications.map((obj) => (
))
) : (
No Applications Found
)}
);
};
export default Applications;
================================================
FILE: frontend/src/component/Home.js
================================================
import { useState, useEffect, useContext } from "react";
import {
Button,
Chip,
Grid,
IconButton,
InputAdornment,
makeStyles,
Paper,
TextField,
Typography,
Modal,
Slider,
FormControlLabel,
FormGroup,
MenuItem,
Checkbox,
} from "@material-ui/core";
import Rating from "@material-ui/lab/Rating";
import Pagination from "@material-ui/lab/Pagination";
import axios from "axios";
import SearchIcon from "@material-ui/icons/Search";
import FilterListIcon from "@material-ui/icons/FilterList";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import { SetPopupContext } from "../App";
import apiList from "../lib/apiList";
import { userType } from "../lib/isAuth";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
button: {
width: "100%",
height: "100%",
},
jobTileOuter: {
padding: "30px",
margin: "20px 0",
boxSizing: "border-box",
width: "100%",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
}));
const JobTile = (props) => {
const classes = useStyles();
const { job } = props;
const setPopup = useContext(SetPopupContext);
const [open, setOpen] = useState(false);
const [sop, setSop] = useState("");
const handleClose = () => {
setOpen(false);
setSop("");
};
const handleApply = () => {
console.log(job._id);
console.log(sop);
axios
.post(
`${apiList.jobs}/${job._id}/applications`,
{
sop: sop,
},
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
handleClose();
})
.catch((err) => {
console.log(err.response);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
handleClose();
});
};
const deadline = new Date(job.deadline).toLocaleDateString();
return (
{job.title}
Role : {job.jobType}
Salary : ₹ {job.salary} per month
Duration :{" "}
{job.duration !== 0 ? `${job.duration} month` : `Flexible`}
Posted By : {job.recruiter.name}
Application Deadline : {deadline}
{job.skillsets.map((skill) => (
))}
{
if (
event.target.value.split(" ").filter(function (n) {
return n != "";
}).length <= 250
) {
setSop(event.target.value);
}
}}
/>
);
};
const FilterPopup = (props) => {
const classes = useStyles();
const { open, handleClose, searchOptions, setSearchOptions, getData } = props;
return (
Job Type
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Full Time"
/>
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Part Time"
/>
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Work From Home"
/>
Salary
{
return value * (100000 / 100);
}}
marks={[
{ value: 0, label: "0" },
{ value: 100, label: "100000" },
]}
value={searchOptions.salary}
onChange={(event, value) =>
setSearchOptions({
...searchOptions,
salary: value,
})
}
/>
Duration
setSearchOptions({
...searchOptions,
duration: event.target.value,
})
}
>
Sort
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
salary: {
...searchOptions.sort.salary,
status: event.target.checked,
},
},
})
}
id="salary"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
salary: {
...searchOptions.sort.salary,
desc: !searchOptions.sort.salary.desc,
},
},
});
}}
>
{searchOptions.sort.salary.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
duration: {
...searchOptions.sort.duration,
status: event.target.checked,
},
},
})
}
id="duration"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
duration: {
...searchOptions.sort.duration,
desc: !searchOptions.sort.duration.desc,
},
},
});
}}
>
{searchOptions.sort.duration.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
rating: {
...searchOptions.sort.rating,
status: event.target.checked,
},
},
})
}
id="rating"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
rating: {
...searchOptions.sort.rating,
desc: !searchOptions.sort.rating.desc,
},
},
});
}}
>
{searchOptions.sort.rating.desc ? (
) : (
)}
);
};
const Home = (props) => {
const [jobs, setJobs] = useState([]);
const [filterOpen, setFilterOpen] = useState(false);
const [searchOptions, setSearchOptions] = useState({
query: "",
jobType: {
fullTime: false,
partTime: false,
wfh: false,
},
salary: [0, 100],
duration: "0",
sort: {
salary: {
status: false,
desc: false,
},
duration: {
status: false,
desc: false,
},
rating: {
status: false,
desc: false,
},
},
});
const setPopup = useContext(SetPopupContext);
useEffect(() => {
getData();
}, []);
const getData = () => {
let searchParams = [];
if (searchOptions.query !== "") {
searchParams = [...searchParams, `q=${searchOptions.query}`];
}
if (searchOptions.jobType.fullTime) {
searchParams = [...searchParams, `jobType=Full%20Time`];
}
if (searchOptions.jobType.partTime) {
searchParams = [...searchParams, `jobType=Part%20Time`];
}
if (searchOptions.jobType.wfh) {
searchParams = [...searchParams, `jobType=Work%20From%20Home`];
}
if (searchOptions.salary[0] != 0) {
searchParams = [
...searchParams,
`salaryMin=${searchOptions.salary[0] * 1000}`,
];
}
if (searchOptions.salary[1] != 100) {
searchParams = [
...searchParams,
`salaryMax=${searchOptions.salary[1] * 1000}`,
];
}
if (searchOptions.duration != "0") {
searchParams = [...searchParams, `duration=${searchOptions.duration}`];
}
let asc = [],
desc = [];
Object.keys(searchOptions.sort).forEach((obj) => {
const item = searchOptions.sort[obj];
if (item.status) {
if (item.desc) {
desc = [...desc, `desc=${obj}`];
} else {
asc = [...asc, `asc=${obj}`];
}
}
});
searchParams = [...searchParams, ...asc, ...desc];
const queryString = searchParams.join("&");
console.log(queryString);
let address = apiList.jobs;
if (queryString !== "") {
address = `${address}?${queryString}`;
}
axios
.get(address, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setJobs(
response.data.filter((obj) => {
const today = new Date();
const deadline = new Date(obj.deadline);
return deadline > today;
})
);
})
.catch((err) => {
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
return (
<>
Jobs
setSearchOptions({
...searchOptions,
query: event.target.value,
})
}
onKeyPress={(ev) => {
if (ev.key === "Enter") {
getData();
}
}}
InputProps={{
endAdornment: (
getData()}>
),
}}
style={{ width: "500px" }}
variant="outlined"
/>
setFilterOpen(true)}>
{jobs.length > 0 ? (
jobs.map((job) => {
return ;
})
) : (
No jobs found
)}
{/*
*/}
setFilterOpen(false)}
getData={() => {
getData();
setFilterOpen(false);
}}
/>
>
);
};
export default Home;
================================================
FILE: frontend/src/component/Login.js
================================================
import { useContext, useState } from "react";
import {
Grid,
TextField,
Button,
Typography,
makeStyles,
Paper,
} from "@material-ui/core";
import axios from "axios";
import { Redirect } from "react-router-dom";
import PasswordInput from "../lib/PasswordInput";
import EmailInput from "../lib/EmailInput";
import { SetPopupContext } from "../App";
import apiList from "../lib/apiList";
import isAuth from "../lib/isAuth";
const useStyles = makeStyles((theme) => ({
body: {
padding: "60px 60px",
},
inputBox: {
width: "300px",
},
submitButton: {
width: "300px",
},
}));
const Login = (props) => {
const classes = useStyles();
const setPopup = useContext(SetPopupContext);
const [loggedin, setLoggedin] = useState(isAuth());
const [loginDetails, setLoginDetails] = useState({
email: "",
password: "",
});
const [inputErrorHandler, setInputErrorHandler] = useState({
email: {
error: false,
message: "",
},
password: {
error: false,
message: "",
},
});
const handleInput = (key, value) => {
setLoginDetails({
...loginDetails,
[key]: value,
});
};
const handleInputError = (key, status, message) => {
setInputErrorHandler({
...inputErrorHandler,
[key]: {
error: status,
message: message,
},
});
};
const handleLogin = () => {
const verified = !Object.keys(inputErrorHandler).some((obj) => {
return inputErrorHandler[obj].error;
});
if (verified) {
axios
.post(apiList.login, loginDetails)
.then((response) => {
localStorage.setItem("token", response.data.token);
localStorage.setItem("type", response.data.type);
setLoggedin(isAuth());
setPopup({
open: true,
severity: "success",
message: "Logged in successfully",
});
console.log(response);
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
} else {
setPopup({
open: true,
severity: "error",
message: "Incorrect Input",
});
}
};
return loggedin ? (
) : (
Login
handleInput("email", event.target.value)}
inputErrorHandler={inputErrorHandler}
handleInputError={handleInputError}
className={classes.inputBox}
/>
handleInput("password", event.target.value)}
className={classes.inputBox}
/>
);
};
export default Login;
================================================
FILE: frontend/src/component/Logout.js
================================================
import { useEffect, useContext } from "react";
import { Redirect } from "react-router-dom";
import { SetPopupContext } from "../App";
const Logout = (props) => {
const setPopup = useContext(SetPopupContext);
useEffect(() => {
localStorage.removeItem("token");
localStorage.removeItem("type");
setPopup({
open: true,
severity: "success",
message: "Logged out successfully",
});
}, []);
return ;
};
export default Logout;
================================================
FILE: frontend/src/component/Navbar.js
================================================
import {
AppBar,
Toolbar,
Typography,
Button,
makeStyles,
} from "@material-ui/core";
import { useHistory } from "react-router-dom";
import isAuth, { userType } from "../lib/isAuth";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
const Navbar = (props) => {
const classes = useStyles();
let history = useHistory();
const handleClick = (location) => {
console.log(location);
history.push(location);
};
return (
Job Portal
{isAuth() ? (
userType() === "recruiter" ? (
<>
>
) : (
<>
>
)
) : (
<>
>
)}
);
};
export default Navbar;
================================================
FILE: frontend/src/component/Profile.js
================================================
import { useContext, useEffect, useState } from "react";
import {
Button,
Grid,
Typography,
Modal,
Paper,
makeStyles,
TextField,
} from "@material-ui/core";
import axios from "axios";
import ChipInput from "material-ui-chip-input";
import FileUploadInput from "../lib/FileUploadInput";
import DescriptionIcon from "@material-ui/icons/Description";
import FaceIcon from "@material-ui/icons/Face";
import { SetPopupContext } from "../App";
import apiList from "../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
// padding: "30px",
},
}));
const MultifieldInput = (props) => {
const classes = useStyles();
const { education, setEducation } = props;
return (
<>
{education.map((obj, key) => (
{
const newEdu = [...education];
newEdu[key].institutionName = event.target.value;
setEducation(newEdu);
}}
variant="outlined"
fullWidth
/>
{
const newEdu = [...education];
newEdu[key].startYear = event.target.value;
setEducation(newEdu);
}}
/>
{
const newEdu = [...education];
newEdu[key].endYear = event.target.value;
setEducation(newEdu);
}}
/>
))}
>
);
};
const Profile = (props) => {
const classes = useStyles();
const setPopup = useContext(SetPopupContext);
const [userData, setUserData] = useState();
const [open, setOpen] = useState(false);
const [profileDetails, setProfileDetails] = useState({
name: "",
education: [],
skills: [],
resume: "",
profile: "",
});
const [education, setEducation] = useState([
{
institutionName: "",
startYear: "",
endYear: "",
},
]);
const handleInput = (key, value) => {
setProfileDetails({
...profileDetails,
[key]: value,
});
};
useEffect(() => {
getData();
}, []);
const getData = () => {
axios
.get(apiList.user, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setProfileDetails(response.data);
if (response.data.education.length > 0) {
setEducation(
response.data.education.map((edu) => ({
institutionName: edu.institutionName ? edu.institutionName : "",
startYear: edu.startYear ? edu.startYear : "",
endYear: edu.endYear ? edu.endYear : "",
}))
);
}
})
.catch((err) => {
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
const handleClose = () => {
setOpen(false);
};
const editDetails = () => {
setOpen(true);
};
const handleUpdate = () => {
console.log(education);
let updatedDetails = {
...profileDetails,
education: education
.filter((obj) => obj.institutionName.trim() !== "")
.map((obj) => {
if (obj["endYear"] === "") {
delete obj["endYear"];
}
return obj;
}),
};
axios
.put(apiList.user, updatedDetails, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
getData();
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
setOpen(false);
};
return (
<>
Profile
handleInput("name", event.target.value)}
className={classes.inputBox}
variant="outlined"
fullWidth
/>
setProfileDetails({
...profileDetails,
skills: [...profileDetails.skills, chip],
})
}
onDelete={(chip, index) => {
let skills = profileDetails.skills;
skills.splice(index, 1);
setProfileDetails({
...profileDetails,
skills: skills,
});
}}
fullWidth
/>
}
uploadTo={apiList.uploadResume}
handleInput={handleInput}
identifier={"resume"}
/>
}
uploadTo={apiList.uploadProfileImage}
handleInput={handleInput}
identifier={"profile"}
/>
{/* */}
{/* */}
>
);
};
export default Profile;
================================================
FILE: frontend/src/component/Signup.js
================================================
import { useState, useContext } from "react";
import {
Grid,
TextField,
Button,
Typography,
makeStyles,
Paper,
MenuItem,
Input,
} from "@material-ui/core";
import axios from "axios";
import { Redirect } from "react-router-dom";
import ChipInput from "material-ui-chip-input";
import DescriptionIcon from "@material-ui/icons/Description";
import FaceIcon from "@material-ui/icons/Face";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/material.css";
import PasswordInput from "../lib/PasswordInput";
import EmailInput from "../lib/EmailInput";
import FileUploadInput from "../lib/FileUploadInput";
import { SetPopupContext } from "../App";
import apiList from "../lib/apiList";
import isAuth from "../lib/isAuth";
const useStyles = makeStyles((theme) => ({
body: {
padding: "60px 60px",
},
inputBox: {
width: "400px",
},
submitButton: {
width: "400px",
},
}));
const MultifieldInput = (props) => {
const classes = useStyles();
const { education, setEducation } = props;
return (
<>
{education.map((obj, key) => (
{
const newEdu = [...education];
newEdu[key].institutionName = event.target.value;
setEducation(newEdu);
}}
variant="outlined"
/>
{
const newEdu = [...education];
newEdu[key].startYear = event.target.value;
setEducation(newEdu);
}}
/>
{
const newEdu = [...education];
newEdu[key].endYear = event.target.value;
setEducation(newEdu);
}}
/>
))}
>
);
};
const Login = (props) => {
const classes = useStyles();
const setPopup = useContext(SetPopupContext);
const [loggedin, setLoggedin] = useState(isAuth());
const [signupDetails, setSignupDetails] = useState({
type: "applicant",
email: "",
password: "",
name: "",
education: [],
skills: [],
resume: "",
profile: "",
bio: "",
contactNumber: "",
});
const [phone, setPhone] = useState("");
const [education, setEducation] = useState([
{
institutionName: "",
startYear: "",
endYear: "",
},
]);
const [inputErrorHandler, setInputErrorHandler] = useState({
email: {
untouched: true,
required: true,
error: false,
message: "",
},
password: {
untouched: true,
required: true,
error: false,
message: "",
},
name: {
untouched: true,
required: true,
error: false,
message: "",
},
});
const handleInput = (key, value) => {
setSignupDetails({
...signupDetails,
[key]: value,
});
};
const handleInputError = (key, status, message) => {
setInputErrorHandler({
...inputErrorHandler,
[key]: {
required: true,
untouched: false,
error: status,
message: message,
},
});
};
const handleLogin = () => {
const tmpErrorHandler = {};
Object.keys(inputErrorHandler).forEach((obj) => {
if (inputErrorHandler[obj].required && inputErrorHandler[obj].untouched) {
tmpErrorHandler[obj] = {
required: true,
untouched: false,
error: true,
message: `${obj[0].toUpperCase() + obj.substr(1)} is required`,
};
} else {
tmpErrorHandler[obj] = inputErrorHandler[obj];
}
});
console.log(education);
let updatedDetails = {
...signupDetails,
education: education
.filter((obj) => obj.institutionName.trim() !== "")
.map((obj) => {
if (obj["endYear"] === "") {
delete obj["endYear"];
}
return obj;
}),
};
setSignupDetails(updatedDetails);
const verified = !Object.keys(tmpErrorHandler).some((obj) => {
return tmpErrorHandler[obj].error;
});
if (verified) {
axios
.post(apiList.signup, updatedDetails)
.then((response) => {
localStorage.setItem("token", response.data.token);
localStorage.setItem("type", response.data.type);
setLoggedin(isAuth());
setPopup({
open: true,
severity: "success",
message: "Logged in successfully",
});
console.log(response);
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
} else {
setInputErrorHandler(tmpErrorHandler);
setPopup({
open: true,
severity: "error",
message: "Incorrect Input",
});
}
};
const handleLoginRecruiter = () => {
const tmpErrorHandler = {};
Object.keys(inputErrorHandler).forEach((obj) => {
if (inputErrorHandler[obj].required && inputErrorHandler[obj].untouched) {
tmpErrorHandler[obj] = {
required: true,
untouched: false,
error: true,
message: `${obj[0].toUpperCase() + obj.substr(1)} is required`,
};
} else {
tmpErrorHandler[obj] = inputErrorHandler[obj];
}
});
let updatedDetails = {
...signupDetails,
};
if (phone !== "") {
updatedDetails = {
...signupDetails,
contactNumber: `+${phone}`,
};
} else {
updatedDetails = {
...signupDetails,
contactNumber: "",
};
}
setSignupDetails(updatedDetails);
const verified = !Object.keys(tmpErrorHandler).some((obj) => {
return tmpErrorHandler[obj].error;
});
console.log(updatedDetails);
if (verified) {
axios
.post(apiList.signup, updatedDetails)
.then((response) => {
localStorage.setItem("token", response.data.token);
localStorage.setItem("type", response.data.type);
setLoggedin(isAuth());
setPopup({
open: true,
severity: "success",
message: "Logged in successfully",
});
console.log(response);
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
} else {
setInputErrorHandler(tmpErrorHandler);
setPopup({
open: true,
severity: "error",
message: "Incorrect Input",
});
}
};
return loggedin ? (
) : (
Signup
{
handleInput("type", event.target.value);
}}
>
handleInput("name", event.target.value)}
className={classes.inputBox}
error={inputErrorHandler.name.error}
helperText={inputErrorHandler.name.message}
onBlur={(event) => {
if (event.target.value === "") {
handleInputError("name", true, "Name is required");
} else {
handleInputError("name", false, "");
}
}}
variant="outlined"
/>
handleInput("email", event.target.value)}
inputErrorHandler={inputErrorHandler}
handleInputError={handleInputError}
className={classes.inputBox}
required={true}
/>
handleInput("password", event.target.value)}
className={classes.inputBox}
error={inputErrorHandler.password.error}
helperText={inputErrorHandler.password.message}
onBlur={(event) => {
if (event.target.value === "") {
handleInputError("password", true, "Password is required");
} else {
handleInputError("password", false, "");
}
}}
/>
{signupDetails.type === "applicant" ? (
<>
setSignupDetails({ ...signupDetails, skills: chips })
}
/>
}
// value={files.resume}
// onChange={(event) =>
// setFiles({
// ...files,
// resume: event.target.files[0],
// })
// }
uploadTo={apiList.uploadResume}
handleInput={handleInput}
identifier={"resume"}
/>
}
// value={files.profileImage}
// onChange={(event) =>
// setFiles({
// ...files,
// profileImage: event.target.files[0],
// })
// }
uploadTo={apiList.uploadProfileImage}
handleInput={handleInput}
identifier={"profile"}
/>
>
) : (
<>
{
if (
event.target.value.split(" ").filter(function (n) {
return n != "";
}).length <= 250
) {
handleInput("bio", event.target.value);
}
}}
/>
setPhone(phone)}
/>
>
)}
);
};
export default Login;
// {/*
// handleInput("tmpPassword", event.target.value)}
// className={classes.inputBox}
// labelWidth={140}
// helperText={inputErrorHandler.tmpPassword.message}
// error={inputErrorHandler.tmpPassword.error}
// onBlur={(event) => {
// if (event.target.value !== signupDetails.password) {
// handleInputError(
// "tmpPassword",
// true,
// "Passwords are not same."
// );
// }
// }}
// />
// */}
================================================
FILE: frontend/src/component/Welcome.js
================================================
import { Grid, Typography } from "@material-ui/core";
const Welcome = (props) => {
return (
Welcome to Job Portal
);
};
export const ErrorPage = (props) => {
return (
Error 404
);
};
export default Welcome;
================================================
FILE: frontend/src/component/recruiter/AcceptedApplicants.js
================================================
import { useState, useEffect, useContext } from "react";
import {
Button,
Chip,
Grid,
IconButton,
InputAdornment,
makeStyles,
Paper,
TextField,
Typography,
Modal,
Slider,
FormControlLabel,
FormGroup,
MenuItem,
Checkbox,
Avatar,
} from "@material-ui/core";
import { useParams } from "react-router-dom";
import Rating from "@material-ui/lab/Rating";
import axios from "axios";
import FilterListIcon from "@material-ui/icons/FilterList";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import { SetPopupContext } from "../../App";
import apiList, { server } from "../../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
statusBlock: {
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
textTransform: "uppercase",
},
jobTileOuter: {
padding: "30px",
margin: "20px 0",
boxSizing: "border-box",
width: "100%",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
avatar: {
width: theme.spacing(17),
height: theme.spacing(17),
},
}));
const FilterPopup = (props) => {
const classes = useStyles();
const { open, handleClose, searchOptions, setSearchOptions, getData } = props;
return (
{/*
Application Status
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Rejected"
/>
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Applied"
/>
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Shortlisted"
/>
*/}
Sort
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.name": {
...searchOptions.sort["jobApplicant.name"],
status: event.target.checked,
},
},
})
}
id="name"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.name": {
...searchOptions.sort["jobApplicant.name"],
desc: !searchOptions.sort["jobApplicant.name"].desc,
},
},
});
}}
>
{searchOptions.sort["jobApplicant.name"].desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"job.title": {
...searchOptions.sort["job.title"],
status: event.target.checked,
},
},
})
}
id="jobTitle"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"job.title": {
...searchOptions.sort["job.title"],
desc: !searchOptions.sort["job.title"].desc,
},
},
});
}}
>
{searchOptions.sort["job.title"].desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
dateOfJoining: {
...searchOptions.sort.dateOfJoining,
status: event.target.checked,
},
},
})
}
id="dateOfJoining"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
dateOfJoining: {
...searchOptions.sort.dateOfJoining,
desc: !searchOptions.sort.dateOfJoining.desc,
},
},
});
}}
>
{searchOptions.sort.dateOfJoining.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.rating": {
...searchOptions.sort[["jobApplicant.rating"]],
status: event.target.checked,
},
},
})
}
id="rating"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.rating": {
...searchOptions.sort["jobApplicant.rating"],
desc: !searchOptions.sort["jobApplicant.rating"]
.desc,
},
},
});
}}
>
{searchOptions.sort["jobApplicant.rating"].desc ? (
) : (
)}
);
};
const ApplicationTile = (props) => {
const classes = useStyles();
const { application, getData } = props;
const setPopup = useContext(SetPopupContext);
const [open, setOpen] = useState(false);
const [openEndJob, setOpenEndJob] = useState(false);
const [rating, setRating] = useState(application.jobApplicant.rating);
const appliedOn = new Date(application.dateOfApplication);
const changeRating = () => {
axios
.put(
apiList.rating,
{ rating: rating, applicantId: application.jobApplicant.userId },
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
)
.then((response) => {
console.log(response.data);
setPopup({
open: true,
severity: "success",
message: "Rating updated successfully",
});
// fetchRating();
getData();
setOpen(false);
})
.catch((err) => {
// console.log(err.response);
console.log(err);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
// fetchRating();
getData();
setOpen(false);
});
};
const handleClose = () => {
setOpen(false);
};
const handleCloseEndJob = () => {
setOpenEndJob(false);
};
const colorSet = {
applied: "#3454D1",
shortlisted: "#DC851F",
accepted: "#09BC8A",
rejected: "#D1345B",
deleted: "#B49A67",
cancelled: "#FF8484",
finished: "#4EA5D9",
};
const getResume = () => {
if (
application.jobApplicant.resume &&
application.jobApplicant.resume !== ""
) {
const address = `${server}${application.jobApplicant.resume}`;
console.log(address);
axios(address, {
method: "GET",
responseType: "blob",
})
.then((response) => {
const file = new Blob([response.data], { type: "application/pdf" });
const fileURL = URL.createObjectURL(file);
window.open(fileURL);
})
.catch((error) => {
console.log(error);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
} else {
setPopup({
open: true,
severity: "error",
message: "No resume found",
});
}
};
const updateStatus = (status) => {
const address = `${apiList.applications}/${application._id}`;
const statusData = {
status: status,
dateOfJoining: new Date().toISOString(),
};
axios
.put(address, statusData, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
handleCloseEndJob();
getData();
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
handleCloseEndJob();
});
};
return (
{application.jobApplicant.name}
Job Title: {application.job.title}
Role: {application.job.jobType}
Applied On: {appliedOn.toLocaleDateString()}
SOP: {application.sop !== "" ? application.sop : "Not Submitted"}
{application.jobApplicant.skills.map((skill) => (
))}
{/* {buttonSet[application.status]} */}
{
setRating(newValue);
}}
/>
Are you sure?
);
};
const AcceptedApplicants = (props) => {
const setPopup = useContext(SetPopupContext);
const [applications, setApplications] = useState([]);
const [filterOpen, setFilterOpen] = useState(false);
const [searchOptions, setSearchOptions] = useState({
sort: {
"jobApplicant.name": {
status: false,
desc: false,
},
"job.title": {
status: false,
desc: false,
},
dateOfJoining: {
status: true,
desc: true,
},
"jobApplicant.rating": {
status: false,
desc: false,
},
},
});
useEffect(() => {
getData();
}, []);
const getData = () => {
let searchParams = [];
searchParams = [...searchParams, `status=accepted`];
let asc = [],
desc = [];
Object.keys(searchOptions.sort).forEach((obj) => {
const item = searchOptions.sort[obj];
if (item.status) {
if (item.desc) {
desc = [...desc, `desc=${obj}`];
} else {
asc = [...asc, `asc=${obj}`];
}
}
});
searchParams = [...searchParams, ...asc, ...desc];
const queryString = searchParams.join("&");
console.log(queryString);
let address = `${apiList.applicants}`;
if (queryString !== "") {
address = `${address}?${queryString}`;
}
console.log(address);
axios
.get(address, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setApplications(response.data);
})
.catch((err) => {
console.log(err.response);
// console.log(err.response.data);
setApplications([]);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
});
};
return (
<>
Employees
setFilterOpen(true)}>
{applications.length > 0 ? (
applications.map((obj) => (
{/* {console.log(obj)} */}
))
) : (
No Applications Found
)}
setFilterOpen(false)}
getData={() => {
getData();
setFilterOpen(false);
}}
/>
>
);
};
export default AcceptedApplicants;
================================================
FILE: frontend/src/component/recruiter/CreateJobs.js
================================================
import { useContext, useEffect, useState } from "react";
import {
Button,
Grid,
Typography,
Modal,
Paper,
makeStyles,
TextField,
MenuItem,
} from "@material-ui/core";
import axios from "axios";
import ChipInput from "material-ui-chip-input";
import { SetPopupContext } from "../../App";
import apiList from "../../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
// padding: "30px",
},
}));
const CreateJobs = (props) => {
const classes = useStyles();
const setPopup = useContext(SetPopupContext);
const [jobDetails, setJobDetails] = useState({
title: "",
maxApplicants: 100,
maxPositions: 30,
deadline: new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000)
.toISOString()
.substr(0, 16),
skillsets: [],
jobType: "Full Time",
duration: 0,
salary: 0,
});
const handleInput = (key, value) => {
setJobDetails({
...jobDetails,
[key]: value,
});
};
const handleUpdate = () => {
console.log(jobDetails);
axios
.post(apiList.jobs, jobDetails, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
setJobDetails({
title: "",
maxApplicants: 100,
maxPositions: 30,
deadline: new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000)
.toISOString()
.substr(0, 16),
skillsets: [],
jobType: "Full Time",
duration: 0,
salary: 0,
});
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
};
return (
<>
Add Job
handleInput("title", event.target.value)
}
variant="outlined"
fullWidth
/>
setJobDetails({
...jobDetails,
skillsets: [...jobDetails.skillsets, chip],
})
}
onDelete={(chip, index) => {
let skillsets = jobDetails.skillsets;
skillsets.splice(index, 1);
setJobDetails({
...jobDetails,
skillsets: skillsets,
});
}}
fullWidth
/>
{
handleInput("jobType", event.target.value);
}}
fullWidth
>
{
handleInput("duration", event.target.value);
}}
fullWidth
>
{
handleInput("salary", event.target.value);
}}
InputProps={{ inputProps: { min: 0 } }}
fullWidth
/>
{
handleInput("deadline", event.target.value);
}}
InputLabelProps={{
shrink: true,
}}
variant="outlined"
fullWidth
/>
{
handleInput("maxApplicants", event.target.value);
}}
InputProps={{ inputProps: { min: 1 } }}
fullWidth
/>
{
handleInput("maxPositions", event.target.value);
}}
InputProps={{ inputProps: { min: 1 } }}
fullWidth
/>
>
);
};
export default CreateJobs;
================================================
FILE: frontend/src/component/recruiter/JobApplications.js
================================================
import { useState, useEffect, useContext } from "react";
import {
Button,
Chip,
Grid,
IconButton,
InputAdornment,
makeStyles,
Paper,
TextField,
Typography,
Modal,
Slider,
FormControlLabel,
FormGroup,
MenuItem,
Checkbox,
Avatar,
} from "@material-ui/core";
import { useParams } from "react-router-dom";
import Rating from "@material-ui/lab/Rating";
import axios from "axios";
import FilterListIcon from "@material-ui/icons/FilterList";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import { SetPopupContext } from "../../App";
import apiList, { server } from "../../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
statusBlock: {
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
textTransform: "uppercase",
},
jobTileOuter: {
padding: "30px",
margin: "20px 0",
boxSizing: "border-box",
width: "100%",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
avatar: {
width: theme.spacing(17),
height: theme.spacing(17),
},
}));
const FilterPopup = (props) => {
const classes = useStyles();
const { open, handleClose, searchOptions, setSearchOptions, getData } = props;
return (
Application Status
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Rejected"
/>
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Applied"
/>
{
setSearchOptions({
...searchOptions,
status: {
...searchOptions.status,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Shortlisted"
/>
Sort
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.name": {
...searchOptions.sort["jobApplicant.name"],
status: event.target.checked,
},
},
})
}
id="name"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.name": {
...searchOptions.sort["jobApplicant.name"],
desc: !searchOptions.sort["jobApplicant.name"].desc,
},
},
});
}}
>
{searchOptions.sort["jobApplicant.name"].desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
dateOfApplication: {
...searchOptions.sort.dateOfApplication,
status: event.target.checked,
},
},
})
}
id="dateOfApplication"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
dateOfApplication: {
...searchOptions.sort.dateOfApplication,
desc: !searchOptions.sort.dateOfApplication.desc,
},
},
});
}}
>
{searchOptions.sort.dateOfApplication.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.rating": {
...searchOptions.sort[["jobApplicant.rating"]],
status: event.target.checked,
},
},
})
}
id="rating"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
"jobApplicant.rating": {
...searchOptions.sort["jobApplicant.rating"],
desc: !searchOptions.sort["jobApplicant.rating"]
.desc,
},
},
});
}}
>
{searchOptions.sort["jobApplicant.rating"].desc ? (
) : (
)}
);
};
const ApplicationTile = (props) => {
const classes = useStyles();
const { application, getData } = props;
const setPopup = useContext(SetPopupContext);
const [open, setOpen] = useState(false);
const appliedOn = new Date(application.dateOfApplication);
const handleClose = () => {
setOpen(false);
};
const colorSet = {
applied: "#3454D1",
shortlisted: "#DC851F",
accepted: "#09BC8A",
rejected: "#D1345B",
deleted: "#B49A67",
cancelled: "#FF8484",
finished: "#4EA5D9",
};
const getResume = () => {
if (
application.jobApplicant.resume &&
application.jobApplicant.resume !== ""
) {
const address = `${server}${application.jobApplicant.resume}`;
console.log(address);
axios(address, {
method: "GET",
responseType: "blob",
})
.then((response) => {
const file = new Blob([response.data], { type: "application/pdf" });
const fileURL = URL.createObjectURL(file);
window.open(fileURL);
})
.catch((error) => {
console.log(error);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
} else {
setPopup({
open: true,
severity: "error",
message: "No resume found",
});
}
};
const updateStatus = (status) => {
const address = `${apiList.applications}/${application._id}`;
const statusData = {
status: status,
dateOfJoining: new Date().toISOString(),
};
axios
.put(address, statusData, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
getData();
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
};
const buttonSet = {
applied: (
<>
>
),
shortlisted: (
<>
>
),
rejected: (
<>
Rejected
>
),
accepted: (
<>
Accepted
>
),
cancelled: (
<>
Cancelled
>
),
finished: (
<>
Finished
>
),
};
return (
{application.jobApplicant.name}
Applied On: {appliedOn.toLocaleDateString()}
Education:{" "}
{application.jobApplicant.education
.map((edu) => {
return `${edu.institutionName} (${edu.startYear}-${
edu.endYear ? edu.endYear : "Ongoing"
})`;
})
.join(", ")}
SOP: {application.sop !== "" ? application.sop : "Not Submitted"}
{application.jobApplicant.skills.map((skill) => (
))}
{buttonSet[application.status]}
);
};
const JobApplications = (props) => {
const setPopup = useContext(SetPopupContext);
const [applications, setApplications] = useState([]);
const { jobId } = useParams();
const [filterOpen, setFilterOpen] = useState(false);
const [searchOptions, setSearchOptions] = useState({
status: {
all: false,
applied: false,
shortlisted: false,
},
sort: {
"jobApplicant.name": {
status: false,
desc: false,
},
dateOfApplication: {
status: true,
desc: true,
},
"jobApplicant.rating": {
status: false,
desc: false,
},
},
});
useEffect(() => {
getData();
}, []);
const getData = () => {
let searchParams = [];
if (searchOptions.status.rejected) {
searchParams = [...searchParams, `status=rejected`];
}
if (searchOptions.status.applied) {
searchParams = [...searchParams, `status=applied`];
}
if (searchOptions.status.shortlisted) {
searchParams = [...searchParams, `status=shortlisted`];
}
let asc = [],
desc = [];
Object.keys(searchOptions.sort).forEach((obj) => {
const item = searchOptions.sort[obj];
if (item.status) {
if (item.desc) {
desc = [...desc, `desc=${obj}`];
} else {
asc = [...asc, `asc=${obj}`];
}
}
});
searchParams = [...searchParams, ...asc, ...desc];
const queryString = searchParams.join("&");
console.log(queryString);
let address = `${apiList.applicants}?jobId=${jobId}`;
if (queryString !== "") {
address = `${address}&${queryString}`;
}
console.log(address);
axios
.get(address, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setApplications(response.data);
})
.catch((err) => {
console.log(err.response);
// console.log(err.response.data);
setApplications([]);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
});
};
return (
<>
Applications
setFilterOpen(true)}>
{applications.length > 0 ? (
applications.map((obj) => (
{/* {console.log(obj)} */}
))
) : (
No Applications Found
)}
setFilterOpen(false)}
getData={() => {
getData();
setFilterOpen(false);
}}
/>
>
);
};
export default JobApplications;
================================================
FILE: frontend/src/component/recruiter/MyJobs.js
================================================
import { useState, useEffect, useContext } from "react";
import {
Button,
Chip,
Grid,
IconButton,
InputAdornment,
makeStyles,
Paper,
TextField,
Typography,
Modal,
Slider,
FormControlLabel,
FormGroup,
MenuItem,
Checkbox,
} from "@material-ui/core";
import { useHistory } from "react-router-dom";
import Rating from "@material-ui/lab/Rating";
import Pagination from "@material-ui/lab/Pagination";
import axios from "axios";
import SearchIcon from "@material-ui/icons/Search";
import FilterListIcon from "@material-ui/icons/FilterList";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import { SetPopupContext } from "../../App";
import apiList from "../../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
button: {
width: "100%",
height: "100%",
},
jobTileOuter: {
padding: "30px",
margin: "20px 0",
boxSizing: "border-box",
width: "100%",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
statusBlock: {
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
textTransform: "uppercase",
},
}));
const JobTile = (props) => {
const classes = useStyles();
let history = useHistory();
const { job, getData } = props;
const setPopup = useContext(SetPopupContext);
const [open, setOpen] = useState(false);
const [openUpdate, setOpenUpdate] = useState(false);
const [jobDetails, setJobDetails] = useState(job);
console.log(jobDetails);
const handleInput = (key, value) => {
setJobDetails({
...jobDetails,
[key]: value,
});
};
const handleClick = (location) => {
history.push(location);
};
const handleClose = () => {
setOpen(false);
};
const handleCloseUpdate = () => {
setOpenUpdate(false);
};
const handleDelete = () => {
console.log(job._id);
axios
.delete(`${apiList.jobs}/${job._id}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
getData();
handleClose();
})
.catch((err) => {
console.log(err.response);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
handleClose();
});
};
const handleJobUpdate = () => {
axios
.put(`${apiList.jobs}/${job._id}`, jobDetails, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
getData();
handleCloseUpdate();
})
.catch((err) => {
console.log(err.response);
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
handleCloseUpdate();
});
};
const postedOn = new Date(job.dateOfPosting);
return (
{job.title}
Role : {job.jobType}
Salary : ₹ {job.salary} per month
Duration :{" "}
{job.duration !== 0 ? `${job.duration} month` : `Flexible`}
Date Of Posting: {postedOn.toLocaleDateString()}
Number of Applicants: {job.maxApplicants}
Remaining Number of Positions:{" "}
{job.maxPositions - job.acceptedCandidates}
{job.skillsets.map((skill) => (
))}
Are you sure?
Update Details
{
handleInput("deadline", event.target.value);
}}
InputLabelProps={{
shrink: true,
}}
variant="outlined"
fullWidth
/>
{
handleInput("maxApplicants", event.target.value);
}}
InputProps={{ inputProps: { min: 1 } }}
fullWidth
/>
{
handleInput("maxPositions", event.target.value);
}}
InputProps={{ inputProps: { min: 1 } }}
fullWidth
/>
);
};
const FilterPopup = (props) => {
const classes = useStyles();
const { open, handleClose, searchOptions, setSearchOptions, getData } = props;
return (
Job Type
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Full Time"
/>
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Part Time"
/>
{
setSearchOptions({
...searchOptions,
jobType: {
...searchOptions.jobType,
[event.target.name]: event.target.checked,
},
});
}}
/>
}
label="Work From Home"
/>
Salary
{
return value * (100000 / 100);
}}
marks={[
{ value: 0, label: "0" },
{ value: 100, label: "100000" },
]}
value={searchOptions.salary}
onChange={(event, value) =>
setSearchOptions({
...searchOptions,
salary: value,
})
}
/>
Duration
setSearchOptions({
...searchOptions,
duration: event.target.value,
})
}
>
Sort
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
salary: {
...searchOptions.sort.salary,
status: event.target.checked,
},
},
})
}
id="salary"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
salary: {
...searchOptions.sort.salary,
desc: !searchOptions.sort.salary.desc,
},
},
});
}}
>
{searchOptions.sort.salary.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
duration: {
...searchOptions.sort.duration,
status: event.target.checked,
},
},
})
}
id="duration"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
duration: {
...searchOptions.sort.duration,
desc: !searchOptions.sort.duration.desc,
},
},
});
}}
>
{searchOptions.sort.duration.desc ? (
) : (
)}
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
rating: {
...searchOptions.sort.rating,
status: event.target.checked,
},
},
})
}
id="rating"
/>
{
setSearchOptions({
...searchOptions,
sort: {
...searchOptions.sort,
rating: {
...searchOptions.sort.rating,
desc: !searchOptions.sort.rating.desc,
},
},
});
}}
>
{searchOptions.sort.rating.desc ? (
) : (
)}
);
};
const MyJobs = (props) => {
const [jobs, setJobs] = useState([]);
const [filterOpen, setFilterOpen] = useState(false);
const [searchOptions, setSearchOptions] = useState({
query: "",
jobType: {
fullTime: false,
partTime: false,
wfh: false,
},
salary: [0, 100],
duration: "0",
sort: {
salary: {
status: false,
desc: false,
},
duration: {
status: false,
desc: false,
},
rating: {
status: false,
desc: false,
},
},
});
const setPopup = useContext(SetPopupContext);
useEffect(() => {
getData();
}, []);
const getData = () => {
let searchParams = [`myjobs=1`];
if (searchOptions.query !== "") {
searchParams = [...searchParams, `q=${searchOptions.query}`];
}
if (searchOptions.jobType.fullTime) {
searchParams = [...searchParams, `jobType=Full%20Time`];
}
if (searchOptions.jobType.partTime) {
searchParams = [...searchParams, `jobType=Part%20Time`];
}
if (searchOptions.jobType.wfh) {
searchParams = [...searchParams, `jobType=Work%20From%20Home`];
}
if (searchOptions.salary[0] != 0) {
searchParams = [
...searchParams,
`salaryMin=${searchOptions.salary[0] * 1000}`,
];
}
if (searchOptions.salary[1] != 100) {
searchParams = [
...searchParams,
`salaryMax=${searchOptions.salary[1] * 1000}`,
];
}
if (searchOptions.duration != "0") {
searchParams = [...searchParams, `duration=${searchOptions.duration}`];
}
let asc = [],
desc = [];
Object.keys(searchOptions.sort).forEach((obj) => {
const item = searchOptions.sort[obj];
if (item.status) {
if (item.desc) {
desc = [...desc, `desc=${obj}`];
} else {
asc = [...asc, `asc=${obj}`];
}
}
});
searchParams = [...searchParams, ...asc, ...desc];
const queryString = searchParams.join("&");
console.log(queryString);
let address = apiList.jobs;
if (queryString !== "") {
address = `${address}?${queryString}`;
}
console.log(address);
axios
.get(address, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setJobs(response.data);
})
.catch((err) => {
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
return (
<>
My Jobs
setSearchOptions({
...searchOptions,
query: event.target.value,
})
}
onKeyPress={(ev) => {
if (ev.key === "Enter") {
getData();
}
}}
InputProps={{
endAdornment: (
getData()}>
),
}}
style={{ width: "500px" }}
variant="outlined"
/>
setFilterOpen(true)}>
{jobs.length > 0 ? (
jobs.map((job) => {
return ;
})
) : (
No jobs found
)}
setFilterOpen(false)}
getData={() => {
getData();
setFilterOpen(false);
}}
/>
>
);
};
export default MyJobs;
================================================
FILE: frontend/src/component/recruiter/Profile.js
================================================
import { useContext, useEffect, useState } from "react";
import {
Button,
Grid,
Typography,
Modal,
Paper,
makeStyles,
TextField,
} from "@material-ui/core";
import axios from "axios";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/material.css";
import { SetPopupContext } from "../../App";
import apiList from "../../lib/apiList";
const useStyles = makeStyles((theme) => ({
body: {
height: "inherit",
},
popupDialog: {
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
// padding: "30px",
},
}));
const Profile = (props) => {
const classes = useStyles();
const setPopup = useContext(SetPopupContext);
const [profileDetails, setProfileDetails] = useState({
name: "",
bio: "",
contactNumber: "",
});
const [phone, setPhone] = useState("");
const handleInput = (key, value) => {
setProfileDetails({
...profileDetails,
[key]: value,
});
};
useEffect(() => {
getData();
}, []);
const getData = () => {
axios
.get(apiList.user, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log(response.data);
setProfileDetails(response.data);
setPhone(response.data.contactNumber);
})
.catch((err) => {
console.log(err.response.data);
setPopup({
open: true,
severity: "error",
message: "Error",
});
});
};
const handleUpdate = () => {
let updatedDetails = {
...profileDetails,
};
if (phone !== "") {
updatedDetails = {
...profileDetails,
contactNumber: `+${phone}`,
};
} else {
updatedDetails = {
...profileDetails,
contactNumber: "",
};
}
axios
.put(apiList.user, updatedDetails, {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
getData();
})
.catch((err) => {
setPopup({
open: true,
severity: "error",
message: err.response.data.message,
});
console.log(err.response);
});
};
return (
<>
Profile
handleInput("name", event.target.value)}
className={classes.inputBox}
variant="outlined"
fullWidth
style={{ width: "100%" }}
/>
{
if (
event.target.value.split(" ").filter(function (n) {
return n != "";
}).length <= 250
) {
handleInput("bio", event.target.value);
}
}}
/>
setPhone(phone)}
style={{ width: "auto" }}
/>
>
);
};
export default Profile;
================================================
FILE: frontend/src/index.css
================================================
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
================================================
FILE: frontend/src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
================================================
FILE: frontend/src/lib/EmailInput.js
================================================
import { TextField } from "@material-ui/core";
const EmailInput = (props) => {
const {
label,
value,
onChange,
inputErrorHandler,
handleInputError,
required,
className,
} = props;
return (
{
if (event.target.value === "") {
if (required) {
handleInputError("email", true, "Email is required");
} else {
handleInputError("email", false, "");
}
} else {
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,}))$/;
if (re.test(String(event.target.value).toLowerCase())) {
handleInputError("email", false, "");
} else {
handleInputError("email", true, "Incorrect email format");
}
}
}}
error={inputErrorHandler.email.error}
className={className}
/>
);
};
export default EmailInput;
================================================
FILE: frontend/src/lib/FileUploadInput.js
================================================
import { useState, useContext } from "react";
import { Grid, Button, TextField, LinearProgress } from "@material-ui/core";
import { CloudUpload } from "@material-ui/icons";
import Axios from "axios";
import { SetPopupContext } from "../App";
const FileUploadInput = (props) => {
const setPopup = useContext(SetPopupContext);
const { uploadTo, identifier, handleInput } = props;
const [file, setFile] = useState("");
const [uploadPercentage, setUploadPercentage] = useState(0);
const handleUpload = () => {
console.log(file);
const data = new FormData();
data.append("file", file);
Axios.post(uploadTo, data, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
setUploadPercentage(
parseInt(
Math.round((progressEvent.loaded * 100) / progressEvent.total)
)
);
},
})
.then((response) => {
console.log(response.data);
handleInput(identifier, response.data.url);
setPopup({
open: true,
severity: "success",
message: response.data.message,
});
})
.catch((err) => {
console.log(err.response);
setPopup({
open: true,
severity: "error",
message: err.response.statusText,
// message: err.response.data
// ? err.response.data.message
// : err.response.statusText,
});
});
};
return (
{uploadPercentage !== 0 ? (
) : null}
);
};
export default FileUploadInput;
================================================
FILE: frontend/src/lib/MessagePopup.js
================================================
import { Snackbar, Slide } from "@material-ui/core";
import { Alert } from "@material-ui/lab";
const MessagePopup = (props) => {
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
props.setOpen(false);
};
return (
{props.message}
);
};
export default MessagePopup;
================================================
FILE: frontend/src/lib/PasswordInput.js
================================================
import { useState } from "react";
import {
FormControl,
InputLabel,
OutlinedInput,
InputAdornment,
IconButton,
FormHelperText,
} from "@material-ui/core";
import Visibility from "@material-ui/icons/Visibility";
import VisibilityOff from "@material-ui/icons/VisibilityOff";
const PasswordInput = (props) => {
const [showPassword, setShowPassword] = useState(false);
const handleShowPassword = () => {
setShowPassword(!showPassword);
};
const handleMouseDownPassword = (event) => {
event.preventDefault();
};
return (
<>
{props.label}
{showPassword ? : }
}
value={props.value}
onChange={(event) => props.onChange(event)}
labelWidth={props.labelWidth ? props.labelWidth : 70}
className={props.className}
onBlur={props.onBlur ? props.onBlur : null}
/>
{props.helperText ? (
{props.helperText}
) : null}
>
);
};
export default PasswordInput;
================================================
FILE: frontend/src/lib/apiList.js
================================================
export const server = "http://localhost:4444";
const apiList = {
login: `${server}/auth/login`,
signup: `${server}/auth/signup`,
uploadResume: `${server}/upload/resume`,
uploadProfileImage: `${server}/upload/profile`,
jobs: `${server}/api/jobs`,
applications: `${server}/api/applications`,
rating: `${server}/api/rating`,
user: `${server}/api/user`,
applicants: `${server}/api/applicants`,
};
export default apiList;
================================================
FILE: frontend/src/lib/isAuth.js
================================================
const isAuth = () => {
return localStorage.getItem("token");
};
export const userType = () => {
return localStorage.getItem("type");
};
export default isAuth;
================================================
FILE: frontend/src/reportWebVitals.js
================================================
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
================================================
FILE: frontend/src/setupTests.js
================================================
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';