├── backend ├── .gitignore ├── controllers │ ├── application.controller.js │ ├── company.controller.js │ ├── job.controller.js │ └── user.controller.js ├── index.js ├── middlewares │ ├── isAuthenticated.js │ └── mutler.js ├── models │ ├── application.model.js │ ├── company.model.js │ ├── job.model.js │ └── user.model.js ├── package-lock.json ├── package.json ├── routes │ ├── application.route.js │ ├── company.route.js │ ├── job.route.js │ └── user.route.js └── utils │ ├── cloudinary.js │ ├── datauri.js │ └── db.js └── frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── components.json ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.css ├── App.jsx ├── assets │ ├── expenselogo.png │ └── react.svg ├── components │ ├── AppliedJobTable.jsx │ ├── Browse.jsx │ ├── CategoryCarousel.jsx │ ├── FilterCard.jsx │ ├── HeroSection.jsx │ ├── Home.jsx │ ├── Job.jsx │ ├── JobDescription.jsx │ ├── Jobs.jsx │ ├── LatestJobCards.jsx │ ├── LatestJobs.jsx │ ├── Profile.jsx │ ├── UpdateProfileDialog.jsx │ ├── admin │ │ ├── AdminJobs.jsx │ │ ├── AdminJobsTable.jsx │ │ ├── Applicants.jsx │ │ ├── ApplicantsTable.jsx │ │ ├── Companies.jsx │ │ ├── CompaniesTable.jsx │ │ ├── CompanyCreate.jsx │ │ ├── CompanySetup.jsx │ │ ├── PostJob.jsx │ │ └── ProtectedRoute.jsx │ ├── auth │ │ ├── Login.jsx │ │ └── Signup.jsx │ ├── shared │ │ ├── Footer.jsx │ │ └── Navbar.jsx │ └── ui │ │ ├── avatar.jsx │ │ ├── badge.jsx │ │ ├── button.jsx │ │ ├── carousel.jsx │ │ ├── dialog.jsx │ │ ├── input.jsx │ │ ├── label.jsx │ │ ├── popover.jsx │ │ ├── radio-group.jsx │ │ ├── select.jsx │ │ ├── sonner.jsx │ │ └── table.jsx ├── hooks │ ├── useGetAllAdminJobs.jsx │ ├── useGetAllCompanies.jsx │ ├── useGetAllJobs.jsx │ ├── useGetAppliedJobs.jsx │ └── useGetCompanyById.jsx ├── index.css ├── lib │ └── utils.js ├── main.jsx ├── redux │ ├── applicationSlice.js │ ├── authSlice.js │ ├── companySlice.js │ ├── jobSlice.js │ └── store.js └── utils │ └── constant.js ├── tailwind.config.js └── vite.config.js /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /backend/controllers/application.controller.js: -------------------------------------------------------------------------------- 1 | import { Application } from "../models/application.model.js"; 2 | import { Job } from "../models/job.model.js"; 3 | 4 | export const applyJob = async (req, res) => { 5 | try { 6 | const userId = req.id; 7 | const jobId = req.params.id; 8 | if (!jobId) { 9 | return res.status(400).json({ 10 | message: "Job id is required.", 11 | success: false 12 | }) 13 | }; 14 | // check if the user has already applied for the job 15 | const existingApplication = await Application.findOne({ job: jobId, applicant: userId }); 16 | 17 | if (existingApplication) { 18 | return res.status(400).json({ 19 | message: "You have already applied for this jobs", 20 | success: false 21 | }); 22 | } 23 | 24 | // check if the jobs exists 25 | const job = await Job.findById(jobId); 26 | if (!job) { 27 | return res.status(404).json({ 28 | message: "Job not found", 29 | success: false 30 | }) 31 | } 32 | // create a new application 33 | const newApplication = await Application.create({ 34 | job:jobId, 35 | applicant:userId, 36 | }); 37 | 38 | job.applications.push(newApplication._id); 39 | await job.save(); 40 | return res.status(201).json({ 41 | message:"Job applied successfully.", 42 | success:true 43 | }) 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | }; 48 | export const getAppliedJobs = async (req,res) => { 49 | try { 50 | const userId = req.id; 51 | const application = await Application.find({applicant:userId}).sort({createdAt:-1}).populate({ 52 | path:'job', 53 | options:{sort:{createdAt:-1}}, 54 | populate:{ 55 | path:'company', 56 | options:{sort:{createdAt:-1}}, 57 | } 58 | }); 59 | if(!application){ 60 | return res.status(404).json({ 61 | message:"No Applications", 62 | success:false 63 | }) 64 | }; 65 | return res.status(200).json({ 66 | application, 67 | success:true 68 | }) 69 | } catch (error) { 70 | console.log(error); 71 | } 72 | } 73 | // admin dekhega kitna user ne apply kiya hai 74 | export const getApplicants = async (req,res) => { 75 | try { 76 | const jobId = req.params.id; 77 | const job = await Job.findById(jobId).populate({ 78 | path:'applications', 79 | options:{sort:{createdAt:-1}}, 80 | populate:{ 81 | path:'applicant' 82 | } 83 | }); 84 | if(!job){ 85 | return res.status(404).json({ 86 | message:'Job not found.', 87 | success:false 88 | }) 89 | }; 90 | return res.status(200).json({ 91 | job, 92 | succees:true 93 | }); 94 | } catch (error) { 95 | console.log(error); 96 | } 97 | } 98 | export const updateStatus = async (req,res) => { 99 | try { 100 | const {status} = req.body; 101 | const applicationId = req.params.id; 102 | if(!status){ 103 | return res.status(400).json({ 104 | message:'status is required', 105 | success:false 106 | }) 107 | }; 108 | 109 | // find the application by applicantion id 110 | const application = await Application.findOne({_id:applicationId}); 111 | if(!application){ 112 | return res.status(404).json({ 113 | message:"Application not found.", 114 | success:false 115 | }) 116 | }; 117 | 118 | // update the status 119 | application.status = status.toLowerCase(); 120 | await application.save(); 121 | 122 | return res.status(200).json({ 123 | message:"Status updated successfully.", 124 | success:true 125 | }); 126 | 127 | } catch (error) { 128 | console.log(error); 129 | } 130 | } -------------------------------------------------------------------------------- /backend/controllers/company.controller.js: -------------------------------------------------------------------------------- 1 | import { Company } from "../models/company.model.js"; 2 | import getDataUri from "../utils/datauri.js"; 3 | import cloudinary from "../utils/cloudinary.js"; 4 | 5 | export const registerCompany = async (req, res) => { 6 | try { 7 | const { companyName } = req.body; 8 | if (!companyName) { 9 | return res.status(400).json({ 10 | message: "Company name is required.", 11 | success: false 12 | }); 13 | } 14 | let company = await Company.findOne({ name: companyName }); 15 | if (company) { 16 | return res.status(400).json({ 17 | message: "You can't register same company.", 18 | success: false 19 | }) 20 | }; 21 | company = await Company.create({ 22 | name: companyName, 23 | userId: req.id 24 | }); 25 | 26 | return res.status(201).json({ 27 | message: "Company registered successfully.", 28 | company, 29 | success: true 30 | }) 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | } 35 | export const getCompany = async (req, res) => { 36 | try { 37 | const userId = req.id; // logged in user id 38 | const companies = await Company.find({ userId }); 39 | if (!companies) { 40 | return res.status(404).json({ 41 | message: "Companies not found.", 42 | success: false 43 | }) 44 | } 45 | return res.status(200).json({ 46 | companies, 47 | success:true 48 | }) 49 | } catch (error) { 50 | console.log(error); 51 | } 52 | } 53 | // get company by id 54 | export const getCompanyById = async (req, res) => { 55 | try { 56 | const companyId = req.params.id; 57 | const company = await Company.findById(companyId); 58 | if (!company) { 59 | return res.status(404).json({ 60 | message: "Company not found.", 61 | success: false 62 | }) 63 | } 64 | return res.status(200).json({ 65 | company, 66 | success: true 67 | }) 68 | } catch (error) { 69 | console.log(error); 70 | } 71 | } 72 | export const updateCompany = async (req, res) => { 73 | try { 74 | const { name, description, website, location } = req.body; 75 | 76 | const file = req.file; 77 | // idhar cloudinary ayega 78 | const fileUri = getDataUri(file); 79 | const cloudResponse = await cloudinary.uploader.upload(fileUri.content); 80 | const logo = cloudResponse.secure_url; 81 | 82 | const updateData = { name, description, website, location, logo }; 83 | 84 | const company = await Company.findByIdAndUpdate(req.params.id, updateData, { new: true }); 85 | 86 | if (!company) { 87 | return res.status(404).json({ 88 | message: "Company not found.", 89 | success: false 90 | }) 91 | } 92 | return res.status(200).json({ 93 | message:"Company information updated.", 94 | success:true 95 | }) 96 | 97 | } catch (error) { 98 | console.log(error); 99 | } 100 | } -------------------------------------------------------------------------------- /backend/controllers/job.controller.js: -------------------------------------------------------------------------------- 1 | import { Job } from "../models/job.model.js"; 2 | 3 | // admin post krega job 4 | export const postJob = async (req, res) => { 5 | try { 6 | const { title, description, requirements, salary, location, jobType, experience, position, companyId } = req.body; 7 | const userId = req.id; 8 | 9 | if (!title || !description || !requirements || !salary || !location || !jobType || !experience || !position || !companyId) { 10 | return res.status(400).json({ 11 | message: "Somethin is missing.", 12 | success: false 13 | }) 14 | }; 15 | const job = await Job.create({ 16 | title, 17 | description, 18 | requirements: requirements.split(","), 19 | salary: Number(salary), 20 | location, 21 | jobType, 22 | experienceLevel: experience, 23 | position, 24 | company: companyId, 25 | created_by: userId 26 | }); 27 | return res.status(201).json({ 28 | message: "New job created successfully.", 29 | job, 30 | success: true 31 | }); 32 | } catch (error) { 33 | console.log(error); 34 | } 35 | } 36 | // student k liye 37 | export const getAllJobs = async (req, res) => { 38 | try { 39 | const keyword = req.query.keyword || ""; 40 | const query = { 41 | $or: [ 42 | { title: { $regex: keyword, $options: "i" } }, 43 | { description: { $regex: keyword, $options: "i" } }, 44 | ] 45 | }; 46 | const jobs = await Job.find(query).populate({ 47 | path: "company" 48 | }).sort({ createdAt: -1 }); 49 | if (!jobs) { 50 | return res.status(404).json({ 51 | message: "Jobs not found.", 52 | success: false 53 | }) 54 | }; 55 | return res.status(200).json({ 56 | jobs, 57 | success: true 58 | }) 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | } 63 | // student 64 | export const getJobById = async (req, res) => { 65 | try { 66 | const jobId = req.params.id; 67 | const job = await Job.findById(jobId).populate({ 68 | path:"applications" 69 | }); 70 | if (!job) { 71 | return res.status(404).json({ 72 | message: "Jobs not found.", 73 | success: false 74 | }) 75 | }; 76 | return res.status(200).json({ job, success: true }); 77 | } catch (error) { 78 | console.log(error); 79 | } 80 | } 81 | // admin kitne job create kra hai abhi tk 82 | export const getAdminJobs = async (req, res) => { 83 | try { 84 | const adminId = req.id; 85 | const jobs = await Job.find({ created_by: adminId }).populate({ 86 | path:'company', 87 | createdAt:-1 88 | }); 89 | if (!jobs) { 90 | return res.status(404).json({ 91 | message: "Jobs not found.", 92 | success: false 93 | }) 94 | }; 95 | return res.status(200).json({ 96 | jobs, 97 | success: true 98 | }) 99 | } catch (error) { 100 | console.log(error); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /backend/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import { User } from "../models/user.model.js"; 2 | import bcrypt from "bcryptjs"; 3 | import jwt from "jsonwebtoken"; 4 | import getDataUri from "../utils/datauri.js"; 5 | import cloudinary from "../utils/cloudinary.js"; 6 | 7 | export const register = async (req, res) => { 8 | try { 9 | const { fullname, email, phoneNumber, password, role } = req.body; 10 | 11 | if (!fullname || !email || !phoneNumber || !password || !role) { 12 | return res.status(400).json({ 13 | message: "Something is missing", 14 | success: false 15 | }); 16 | }; 17 | const file = req.file; 18 | const fileUri = getDataUri(file); 19 | const cloudResponse = await cloudinary.uploader.upload(fileUri.content); 20 | 21 | const user = await User.findOne({ email }); 22 | if (user) { 23 | return res.status(400).json({ 24 | message: 'User already exist with this email.', 25 | success: false, 26 | }) 27 | } 28 | const hashedPassword = await bcrypt.hash(password, 10); 29 | 30 | await User.create({ 31 | fullname, 32 | email, 33 | phoneNumber, 34 | password: hashedPassword, 35 | role, 36 | profile:{ 37 | profilePhoto:cloudResponse.secure_url, 38 | } 39 | }); 40 | 41 | return res.status(201).json({ 42 | message: "Account created successfully.", 43 | success: true 44 | }); 45 | } catch (error) { 46 | console.log(error); 47 | } 48 | } 49 | export const login = async (req, res) => { 50 | try { 51 | const { email, password, role } = req.body; 52 | 53 | if (!email || !password || !role) { 54 | return res.status(400).json({ 55 | message: "Something is missing", 56 | success: false 57 | }); 58 | }; 59 | let user = await User.findOne({ email }); 60 | if (!user) { 61 | return res.status(400).json({ 62 | message: "Incorrect email or password.", 63 | success: false, 64 | }) 65 | } 66 | const isPasswordMatch = await bcrypt.compare(password, user.password); 67 | if (!isPasswordMatch) { 68 | return res.status(400).json({ 69 | message: "Incorrect email or password.", 70 | success: false, 71 | }) 72 | }; 73 | // check role is correct or not 74 | if (role !== user.role) { 75 | return res.status(400).json({ 76 | message: "Account doesn't exist with current role.", 77 | success: false 78 | }) 79 | }; 80 | 81 | const tokenData = { 82 | userId: user._id 83 | } 84 | const token = await jwt.sign(tokenData, process.env.SECRET_KEY, { expiresIn: '1d' }); 85 | 86 | user = { 87 | _id: user._id, 88 | fullname: user.fullname, 89 | email: user.email, 90 | phoneNumber: user.phoneNumber, 91 | role: user.role, 92 | profile: user.profile 93 | } 94 | 95 | return res.status(200).cookie("token", token, { maxAge: 1 * 24 * 60 * 60 * 1000, httpsOnly: true, sameSite: 'strict' }).json({ 96 | message: `Welcome back ${user.fullname}`, 97 | user, 98 | success: true 99 | }) 100 | } catch (error) { 101 | console.log(error); 102 | } 103 | } 104 | export const logout = async (req, res) => { 105 | try { 106 | return res.status(200).cookie("token", "", { maxAge: 0 }).json({ 107 | message: "Logged out successfully.", 108 | success: true 109 | }) 110 | } catch (error) { 111 | console.log(error); 112 | } 113 | } 114 | export const updateProfile = async (req, res) => { 115 | try { 116 | const { fullname, email, phoneNumber, bio, skills } = req.body; 117 | 118 | const file = req.file; 119 | // cloudinary ayega idhar 120 | const fileUri = getDataUri(file); 121 | const cloudResponse = await cloudinary.uploader.upload(fileUri.content); 122 | 123 | 124 | 125 | let skillsArray; 126 | if(skills){ 127 | skillsArray = skills.split(","); 128 | } 129 | const userId = req.id; // middleware authentication 130 | let user = await User.findById(userId); 131 | 132 | if (!user) { 133 | return res.status(400).json({ 134 | message: "User not found.", 135 | success: false 136 | }) 137 | } 138 | // updating data 139 | if(fullname) user.fullname = fullname 140 | if(email) user.email = email 141 | if(phoneNumber) user.phoneNumber = phoneNumber 142 | if(bio) user.profile.bio = bio 143 | if(skills) user.profile.skills = skillsArray 144 | 145 | // resume comes later here... 146 | if(cloudResponse){ 147 | user.profile.resume = cloudResponse.secure_url // save the cloudinary url 148 | user.profile.resumeOriginalName = file.originalname // Save the original file name 149 | } 150 | 151 | 152 | await user.save(); 153 | 154 | user = { 155 | _id: user._id, 156 | fullname: user.fullname, 157 | email: user.email, 158 | phoneNumber: user.phoneNumber, 159 | role: user.role, 160 | profile: user.profile 161 | } 162 | 163 | return res.status(200).json({ 164 | message:"Profile updated successfully.", 165 | user, 166 | success:true 167 | }) 168 | } catch (error) { 169 | console.log(error); 170 | } 171 | } -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import cookieParser from "cookie-parser"; 3 | import cors from "cors"; 4 | import dotenv from "dotenv"; 5 | import connectDB from "./utils/db.js"; 6 | import userRoute from "./routes/user.route.js"; 7 | import companyRoute from "./routes/company.route.js"; 8 | import jobRoute from "./routes/job.route.js"; 9 | import applicationRoute from "./routes/application.route.js"; 10 | 11 | dotenv.config({}); 12 | 13 | const app = express(); 14 | 15 | // middleware 16 | app.use(express.json()); 17 | app.use(express.urlencoded({extended:true})); 18 | app.use(cookieParser()); 19 | const corsOptions = { 20 | origin:'http://localhost:5173', 21 | credentials:true 22 | } 23 | 24 | app.use(cors(corsOptions)); 25 | 26 | const PORT = process.env.PORT || 3000; 27 | 28 | 29 | // api's 30 | app.use("/api/v1/user", userRoute); 31 | app.use("/api/v1/company", companyRoute); 32 | app.use("/api/v1/job", jobRoute); 33 | app.use("/api/v1/application", applicationRoute); 34 | 35 | 36 | 37 | app.listen(PORT,()=>{ 38 | connectDB(); 39 | console.log(`Server running at port ${PORT}`); 40 | }) -------------------------------------------------------------------------------- /backend/middlewares/isAuthenticated.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | const isAuthenticated = async (req, res, next) => { 4 | try { 5 | const token = req.cookies.token; 6 | if (!token) { 7 | return res.status(401).json({ 8 | message: "User not authenticated", 9 | success: false, 10 | }) 11 | } 12 | const decode = await jwt.verify(token, process.env.SECRET_KEY); 13 | if(!decode){ 14 | return res.status(401).json({ 15 | message:"Invalid token", 16 | success:false 17 | }) 18 | }; 19 | req.id = decode.userId; 20 | next(); 21 | } catch (error) { 22 | console.log(error); 23 | } 24 | } 25 | export default isAuthenticated; -------------------------------------------------------------------------------- /backend/middlewares/mutler.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | 3 | const storage = multer.memoryStorage(); 4 | export const singleUpload = multer({storage}).single("file"); -------------------------------------------------------------------------------- /backend/models/application.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const applicationSchema = new mongoose.Schema({ 4 | job:{ 5 | type:mongoose.Schema.Types.ObjectId, 6 | ref:'Job', 7 | required:true 8 | }, 9 | applicant:{ 10 | type:mongoose.Schema.Types.ObjectId, 11 | ref:'User', 12 | required:true 13 | }, 14 | status:{ 15 | type:String, 16 | enum:['pending', 'accepted', 'rejected'], 17 | default:'pending' 18 | } 19 | },{timestamps:true}); 20 | export const Application = mongoose.model("Application", applicationSchema); -------------------------------------------------------------------------------- /backend/models/company.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const companySchema = new mongoose.Schema({ 4 | name:{ 5 | type:String, 6 | required:true, 7 | unique:true 8 | }, 9 | description:{ 10 | type:String, 11 | }, 12 | website:{ 13 | type:String 14 | }, 15 | location:{ 16 | type:String 17 | }, 18 | logo:{ 19 | type:String // URL to company logo 20 | }, 21 | userId:{ 22 | type:mongoose.Schema.Types.ObjectId, 23 | ref:'User', 24 | required:true 25 | } 26 | },{timestamps:true}) 27 | export const Company = mongoose.model("Company", companySchema); -------------------------------------------------------------------------------- /backend/models/job.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const jobSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true 7 | }, 8 | description: { 9 | type: String, 10 | required: true 11 | }, 12 | requirements: [{ 13 | type: String 14 | }], 15 | salary: { 16 | type: Number, 17 | required: true 18 | }, 19 | experienceLevel:{ 20 | type:Number, 21 | required:true, 22 | }, 23 | location: { 24 | type: String, 25 | required: true 26 | }, 27 | jobType: { 28 | type: String, 29 | required: true 30 | }, 31 | position: { 32 | type: Number, 33 | required: true 34 | }, 35 | company: { 36 | type: mongoose.Schema.Types.ObjectId, 37 | ref: 'Company', 38 | required: true 39 | }, 40 | created_by: { 41 | type: mongoose.Schema.Types.ObjectId, 42 | ref: 'User', 43 | required: true 44 | }, 45 | applications: [ 46 | { 47 | type: mongoose.Schema.Types.ObjectId, 48 | ref: 'Application', 49 | } 50 | ] 51 | },{timestamps:true}); 52 | export const Job = mongoose.model("Job", jobSchema); -------------------------------------------------------------------------------- /backend/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | fullname: { 5 | type: String, 6 | required: true 7 | }, 8 | email: { 9 | type: String, 10 | required: true, 11 | unique: true 12 | }, 13 | phoneNumber: { 14 | type: Number, 15 | required: true 16 | }, 17 | password:{ 18 | type:String, 19 | required:true, 20 | }, 21 | role:{ 22 | type:String, 23 | enum:['student','recruiter'], 24 | required:true 25 | }, 26 | profile:{ 27 | bio:{type:String}, 28 | skills:[{type:String}], 29 | resume:{type:String}, // URL to resume file 30 | resumeOriginalName:{type:String}, 31 | company:{type:mongoose.Schema.Types.ObjectId, ref:'Company'}, 32 | profilePhoto:{ 33 | type:String, 34 | default:"" 35 | } 36 | }, 37 | },{timestamps:true}); 38 | export const User = mongoose.model('User', userSchema); -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcryptjs": "^2.4.3", 15 | "cloudinary": "^2.3.0", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "datauri": "^4.1.0", 19 | "dotenv": "^16.4.5", 20 | "express": "^4.19.2", 21 | "jsonwebtoken": "^9.0.2", 22 | "mongoose": "^8.4.1", 23 | "multer": "^1.4.5-lts.1", 24 | "nodemon": "^3.1.3" 25 | }, 26 | "devDependencies": { 27 | "@types/bcryptjs": "^2.4.6", 28 | "@types/cookie-parser": "^1.4.7", 29 | "@types/express": "^4.17.21", 30 | "@types/jsonwebtoken": "^9.0.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/routes/application.route.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuthenticated from "../middlewares/isAuthenticated.js"; 3 | import { applyJob, getApplicants, getAppliedJobs, updateStatus } from "../controllers/application.controller.js"; 4 | 5 | const router = express.Router(); 6 | 7 | router.route("/apply/:id").get(isAuthenticated, applyJob); 8 | router.route("/get").get(isAuthenticated, getAppliedJobs); 9 | router.route("/:id/applicants").get(isAuthenticated, getApplicants); 10 | router.route("/status/:id/update").post(isAuthenticated, updateStatus); 11 | 12 | 13 | export default router; 14 | 15 | -------------------------------------------------------------------------------- /backend/routes/company.route.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuthenticated from "../middlewares/isAuthenticated.js"; 3 | import { getCompany, getCompanyById, registerCompany, updateCompany } from "../controllers/company.controller.js"; 4 | import { singleUpload } from "../middlewares/mutler.js"; 5 | 6 | const router = express.Router(); 7 | 8 | router.route("/register").post(isAuthenticated,registerCompany); 9 | router.route("/get").get(isAuthenticated,getCompany); 10 | router.route("/get/:id").get(isAuthenticated,getCompanyById); 11 | router.route("/update/:id").put(isAuthenticated,singleUpload, updateCompany); 12 | 13 | export default router; 14 | 15 | -------------------------------------------------------------------------------- /backend/routes/job.route.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import isAuthenticated from "../middlewares/isAuthenticated.js"; 3 | import { getAdminJobs, getAllJobs, getJobById, postJob } from "../controllers/job.controller.js"; 4 | 5 | const router = express.Router(); 6 | 7 | router.route("/post").post(isAuthenticated, postJob); 8 | router.route("/get").get(isAuthenticated, getAllJobs); 9 | router.route("/getadminjobs").get(isAuthenticated, getAdminJobs); 10 | router.route("/get/:id").get(isAuthenticated, getJobById); 11 | 12 | export default router; 13 | 14 | -------------------------------------------------------------------------------- /backend/routes/user.route.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { login, logout, register, updateProfile } from "../controllers/user.controller.js"; 3 | import isAuthenticated from "../middlewares/isAuthenticated.js"; 4 | import { singleUpload } from "../middlewares/mutler.js"; 5 | 6 | const router = express.Router(); 7 | 8 | router.route("/register").post(singleUpload,register); 9 | router.route("/login").post(login); 10 | router.route("/logout").get(logout); 11 | router.route("/profile/update").post(isAuthenticated,singleUpload,updateProfile); 12 | 13 | export default router; 14 | 15 | -------------------------------------------------------------------------------- /backend/utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | import {v2 as cloudinary} from "cloudinary"; 2 | import dotenv from "dotenv"; 3 | dotenv.config(); 4 | 5 | cloudinary.config({ 6 | cloud_name:process.env.CLOUD_NAME, 7 | api_key:process.env.API_KEY, 8 | api_secret:process.env.API_SECRET 9 | }); 10 | export default cloudinary; -------------------------------------------------------------------------------- /backend/utils/datauri.js: -------------------------------------------------------------------------------- 1 | import DataUriParser from "datauri/parser.js" 2 | 3 | import path from "path"; 4 | 5 | const getDataUri = (file) => { 6 | const parser = new DataUriParser(); 7 | const extName = path.extname(file.originalname).toString(); 8 | return parser.format(extName, file.buffer); 9 | } 10 | 11 | export default getDataUri; -------------------------------------------------------------------------------- /backend/utils/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectDB = async () => { 4 | try { 5 | await mongoose.connect(process.env.MONGO_URI); 6 | console.log('mongodb connected successfully'); 7 | } catch (error) { 8 | console.log(error); 9 | } 10 | } 11 | export default connectDB; -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // ... 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": [ 7 | "./src/*" 8 | ] 9 | } 10 | // ... 11 | } 12 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-avatar": "^1.0.4", 14 | "@radix-ui/react-dialog": "^1.1.1", 15 | "@radix-ui/react-label": "^2.0.2", 16 | "@radix-ui/react-popover": "^1.0.7", 17 | "@radix-ui/react-radio-group": "^1.2.0", 18 | "@radix-ui/react-select": "^2.1.1", 19 | "@radix-ui/react-slot": "^1.1.0", 20 | "@reduxjs/toolkit": "^2.2.6", 21 | "axios": "^1.7.2", 22 | "class-variance-authority": "^0.7.0", 23 | "clsx": "^2.1.1", 24 | "embla-carousel-react": "^8.1.6", 25 | "framer-motion": "^11.3.7", 26 | "lucide-react": "^0.395.0", 27 | "next-themes": "^0.3.0", 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0", 30 | "react-redux": "^9.1.2", 31 | "react-router-dom": "^6.23.1", 32 | "redux-persist": "^6.0.0", 33 | "sonner": "^1.5.0", 34 | "tailwind-merge": "^2.3.0", 35 | "tailwindcss-animate": "^1.0.7" 36 | }, 37 | "devDependencies": { 38 | "@types/react": "^18.2.66", 39 | "@types/react-dom": "^18.2.22", 40 | "@vitejs/plugin-react": "^4.2.1", 41 | "autoprefixer": "^10.4.19", 42 | "eslint": "^8.57.0", 43 | "eslint-plugin-react": "^7.34.1", 44 | "eslint-plugin-react-hooks": "^4.6.0", 45 | "eslint-plugin-react-refresh": "^0.4.6", 46 | "postcss": "^8.4.38", 47 | "tailwindcss": "^3.4.4", 48 | "vite": "^5.2.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surendrakumarpatel/jobportal-yt/8b6e36c22bc24d447b57e4e525f1470967c47295/frontend/src/App.css -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter, RouterProvider } from 'react-router-dom' 2 | import Navbar from './components/shared/Navbar' 3 | import Login from './components/auth/Login' 4 | import Signup from './components/auth/Signup' 5 | import Home from './components/Home' 6 | import Jobs from './components/Jobs' 7 | import Browse from './components/Browse' 8 | import Profile from './components/Profile' 9 | import JobDescription from './components/JobDescription' 10 | import Companies from './components/admin/Companies' 11 | import CompanyCreate from './components/admin/CompanyCreate' 12 | import CompanySetup from './components/admin/CompanySetup' 13 | import AdminJobs from "./components/admin/AdminJobs"; 14 | import PostJob from './components/admin/PostJob' 15 | import Applicants from './components/admin/Applicants' 16 | import ProtectedRoute from './components/admin/ProtectedRoute' 17 | 18 | 19 | const appRouter = createBrowserRouter([ 20 | { 21 | path: '/', 22 | element: 23 | }, 24 | { 25 | path: '/login', 26 | element: 27 | }, 28 | { 29 | path: '/signup', 30 | element: 31 | }, 32 | { 33 | path: "/jobs", 34 | element: 35 | }, 36 | { 37 | path: "/description/:id", 38 | element: 39 | }, 40 | { 41 | path: "/browse", 42 | element: 43 | }, 44 | { 45 | path: "/profile", 46 | element: 47 | }, 48 | // admin ke liye yha se start hoga 49 | { 50 | path:"/admin/companies", 51 | element: 52 | }, 53 | { 54 | path:"/admin/companies/create", 55 | element: 56 | }, 57 | { 58 | path:"/admin/companies/:id", 59 | element: 60 | }, 61 | { 62 | path:"/admin/jobs", 63 | element: 64 | }, 65 | { 66 | path:"/admin/jobs/create", 67 | element: 68 | }, 69 | { 70 | path:"/admin/jobs/:id/applicants", 71 | element: 72 | }, 73 | 74 | ]) 75 | function App() { 76 | 77 | return ( 78 |
79 | 80 |
81 | ) 82 | } 83 | 84 | export default App 85 | -------------------------------------------------------------------------------- /frontend/src/assets/expenselogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surendrakumarpatel/jobportal-yt/8b6e36c22bc24d447b57e4e525f1470967c47295/frontend/src/assets/expenselogo.png -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/AppliedJobTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from './ui/table' 3 | import { Badge } from './ui/badge' 4 | import { useSelector } from 'react-redux' 5 | 6 | const AppliedJobTable = () => { 7 | const {allAppliedJobs} = useSelector(store=>store.job); 8 | return ( 9 |
10 | 11 | A list of your applied jobs 12 | 13 | 14 | Date 15 | Job Role 16 | Company 17 | Status 18 | 19 | 20 | 21 | { 22 | allAppliedJobs.length <= 0 ? You haven't applied any job yet. : allAppliedJobs.map((appliedJob) => ( 23 | 24 | {appliedJob?.createdAt?.split("T")[0]} 25 | {appliedJob.job?.title} 26 | {appliedJob.job?.company?.name} 27 | {appliedJob.status.toUpperCase()} 28 | 29 | )) 30 | } 31 | 32 |
33 |
34 | ) 35 | } 36 | 37 | export default AppliedJobTable -------------------------------------------------------------------------------- /frontend/src/components/Browse.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import Navbar from './shared/Navbar' 3 | import Job from './Job'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { setSearchedQuery } from '@/redux/jobSlice'; 6 | import useGetAllJobs from '@/hooks/useGetAllJobs'; 7 | 8 | // const randomJobs = [1, 2,45]; 9 | 10 | const Browse = () => { 11 | useGetAllJobs(); 12 | const {allJobs} = useSelector(store=>store.job); 13 | const dispatch = useDispatch(); 14 | useEffect(()=>{ 15 | return ()=>{ 16 | dispatch(setSearchedQuery("")); 17 | } 18 | },[]) 19 | return ( 20 |
21 | 22 |
23 |

Search Results ({allJobs.length})

24 |
25 | { 26 | allJobs.map((job) => { 27 | return ( 28 | 29 | ) 30 | }) 31 | } 32 |
33 | 34 |
35 |
36 | ) 37 | } 38 | 39 | export default Browse -------------------------------------------------------------------------------- /frontend/src/components/CategoryCarousel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from './ui/carousel'; 3 | import { Button } from './ui/button'; 4 | import { useDispatch } from 'react-redux'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { setSearchedQuery } from '@/redux/jobSlice'; 7 | 8 | const category = [ 9 | "Frontend Developer", 10 | "Backend Developer", 11 | "Data Science", 12 | "Graphic Designer", 13 | "FullStack Developer" 14 | ] 15 | 16 | const CategoryCarousel = () => { 17 | const dispatch = useDispatch(); 18 | const navigate = useNavigate(); 19 | const searchJobHandler = (query) => { 20 | dispatch(setSearchedQuery(query)); 21 | navigate("/browse"); 22 | } 23 | 24 | return ( 25 |
26 | 27 | 28 | { 29 | category.map((cat, index) => ( 30 | 31 | 32 | 33 | )) 34 | } 35 | 36 | 37 | 38 | 39 |
40 | ) 41 | } 42 | 43 | export default CategoryCarousel -------------------------------------------------------------------------------- /frontend/src/components/FilterCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { RadioGroup, RadioGroupItem } from './ui/radio-group' 3 | import { Label } from './ui/label' 4 | import { useDispatch } from 'react-redux' 5 | import { setSearchedQuery } from '@/redux/jobSlice' 6 | 7 | const fitlerData = [ 8 | { 9 | fitlerType: "Location", 10 | array: ["Delhi NCR", "Bangalore", "Hyderabad", "Pune", "Mumbai"] 11 | }, 12 | { 13 | fitlerType: "Industry", 14 | array: ["Frontend Developer", "Backend Developer", "FullStack Developer"] 15 | }, 16 | { 17 | fitlerType: "Salary", 18 | array: ["0-40k", "42-1lakh", "1lakh to 5lakh"] 19 | }, 20 | ] 21 | 22 | const FilterCard = () => { 23 | const [selectedValue, setSelectedValue] = useState(''); 24 | const dispatch = useDispatch(); 25 | const changeHandler = (value) => { 26 | setSelectedValue(value); 27 | } 28 | useEffect(()=>{ 29 | dispatch(setSearchedQuery(selectedValue)); 30 | },[selectedValue]); 31 | return ( 32 |
33 |

Filter Jobs

34 |
35 | 36 | { 37 | fitlerData.map((data, index) => ( 38 |
39 |

{data.fitlerType}

40 | { 41 | data.array.map((item, idx) => { 42 | const itemId = `id${index}-${idx}` 43 | return ( 44 |
45 | 46 | 47 |
48 | ) 49 | }) 50 | } 51 |
52 | )) 53 | } 54 |
55 |
56 | ) 57 | } 58 | 59 | export default FilterCard -------------------------------------------------------------------------------- /frontend/src/components/HeroSection.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Button } from './ui/button' 3 | import { Search } from 'lucide-react' 4 | import { useDispatch } from 'react-redux'; 5 | import { setSearchedQuery } from '@/redux/jobSlice'; 6 | import { useNavigate } from 'react-router-dom'; 7 | 8 | const HeroSection = () => { 9 | const [query, setQuery] = useState(""); 10 | const dispatch = useDispatch(); 11 | const navigate = useNavigate(); 12 | 13 | const searchJobHandler = () => { 14 | dispatch(setSearchedQuery(query)); 15 | navigate("/browse"); 16 | } 17 | 18 | return ( 19 |
20 |
21 | No. 1 Job Hunt Website 22 |

Search, Apply &
Get Your Dream Jobs

23 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid aspernatur temporibus nihil tempora dolor!

24 |
25 | setQuery(e.target.value)} 29 | className='outline-none border-none w-full' 30 | 31 | /> 32 | 35 |
36 |
37 |
38 | ) 39 | } 40 | 41 | export default HeroSection -------------------------------------------------------------------------------- /frontend/src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import Navbar from './shared/Navbar' 3 | import HeroSection from './HeroSection' 4 | import CategoryCarousel from './CategoryCarousel' 5 | import LatestJobs from './LatestJobs' 6 | import Footer from './shared/Footer' 7 | import useGetAllJobs from '@/hooks/useGetAllJobs' 8 | import { useSelector } from 'react-redux' 9 | import { useNavigate } from 'react-router-dom' 10 | 11 | const Home = () => { 12 | useGetAllJobs(); 13 | const { user } = useSelector(store => store.auth); 14 | const navigate = useNavigate(); 15 | useEffect(() => { 16 | if (user?.role === 'recruiter') { 17 | navigate("/admin/companies"); 18 | } 19 | }, []); 20 | return ( 21 |
22 | 23 | 24 | 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default Home -------------------------------------------------------------------------------- /frontend/src/components/Job.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from './ui/button' 3 | import { Bookmark } from 'lucide-react' 4 | import { Avatar, AvatarImage } from './ui/avatar' 5 | import { Badge } from './ui/badge' 6 | import { useNavigate } from 'react-router-dom' 7 | 8 | const Job = ({job}) => { 9 | const navigate = useNavigate(); 10 | // const jobId = "lsekdhjgdsnfvsdkjf"; 11 | 12 | const daysAgoFunction = (mongodbTime) => { 13 | const createdAt = new Date(mongodbTime); 14 | const currentTime = new Date(); 15 | const timeDifference = currentTime - createdAt; 16 | return Math.floor(timeDifference/(1000*24*60*60)); 17 | } 18 | 19 | return ( 20 |
21 |
22 |

{daysAgoFunction(job?.createdAt) === 0 ? "Today" : `${daysAgoFunction(job?.createdAt)} days ago`}

23 | 24 |
25 | 26 |
27 | 32 |
33 |

{job?.company?.name}

34 |

India

35 |
36 |
37 | 38 |
39 |

{job?.title}

40 |

{job?.description}

41 |
42 |
43 | {job?.position} Positions 44 | {job?.jobType} 45 | {job?.salary}LPA 46 |
47 |
48 | 49 | 50 |
51 |
52 | ) 53 | } 54 | 55 | export default Job -------------------------------------------------------------------------------- /frontend/src/components/JobDescription.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Badge } from './ui/badge' 3 | import { Button } from './ui/button' 4 | import { useParams } from 'react-router-dom'; 5 | import axios from 'axios'; 6 | import { APPLICATION_API_END_POINT, JOB_API_END_POINT } from '@/utils/constant'; 7 | import { setSingleJob } from '@/redux/jobSlice'; 8 | import { useDispatch, useSelector } from 'react-redux'; 9 | import { toast } from 'sonner'; 10 | 11 | const JobDescription = () => { 12 | const {singleJob} = useSelector(store => store.job); 13 | const {user} = useSelector(store=>store.auth); 14 | const isIntiallyApplied = singleJob?.applications?.some(application => application.applicant === user?._id) || false; 15 | const [isApplied, setIsApplied] = useState(isIntiallyApplied); 16 | 17 | const params = useParams(); 18 | const jobId = params.id; 19 | const dispatch = useDispatch(); 20 | 21 | const applyJobHandler = async () => { 22 | try { 23 | const res = await axios.get(`${APPLICATION_API_END_POINT}/apply/${jobId}`, {withCredentials:true}); 24 | 25 | if(res.data.success){ 26 | setIsApplied(true); // Update the local state 27 | const updatedSingleJob = {...singleJob, applications:[...singleJob.applications,{applicant:user?._id}]} 28 | dispatch(setSingleJob(updatedSingleJob)); // helps us to real time UI update 29 | toast.success(res.data.message); 30 | 31 | } 32 | } catch (error) { 33 | console.log(error); 34 | toast.error(error.response.data.message); 35 | } 36 | } 37 | 38 | useEffect(()=>{ 39 | const fetchSingleJob = async () => { 40 | try { 41 | const res = await axios.get(`${JOB_API_END_POINT}/get/${jobId}`,{withCredentials:true}); 42 | if(res.data.success){ 43 | dispatch(setSingleJob(res.data.job)); 44 | setIsApplied(res.data.job.applications.some(application=>application.applicant === user?._id)) // Ensure the state is in sync with fetched data 45 | } 46 | } catch (error) { 47 | console.log(error); 48 | } 49 | } 50 | fetchSingleJob(); 51 | },[jobId,dispatch, user?._id]); 52 | 53 | return ( 54 |
55 |
56 |
57 |

{singleJob?.title}

58 |
59 | {singleJob?.postion} Positions 60 | {singleJob?.jobType} 61 | {singleJob?.salary}LPA 62 |
63 |
64 | 70 |
71 |

Job Description

72 |
73 |

Role: {singleJob?.title}

74 |

Location: {singleJob?.location}

75 |

Description: {singleJob?.description}

76 |

Experience: {singleJob?.experience} yrs

77 |

Salary: {singleJob?.salary}LPA

78 |

Total Applicants: {singleJob?.applications?.length}

79 |

Posted Date: {singleJob?.createdAt.split("T")[0]}

80 |
81 |
82 | ) 83 | } 84 | 85 | export default JobDescription -------------------------------------------------------------------------------- /frontend/src/components/Jobs.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from './shared/Navbar' 3 | import FilterCard from './FilterCard' 4 | import Job from './Job'; 5 | import { useSelector } from 'react-redux'; 6 | import { motion } from 'framer-motion'; 7 | 8 | // const jobsArray = [1, 2, 3, 4, 5, 6, 7, 8]; 9 | 10 | const Jobs = () => { 11 | const { allJobs, searchedQuery } = useSelector(store => store.job); 12 | const [filterJobs, setFilterJobs] = useState(allJobs); 13 | 14 | useEffect(() => { 15 | if (searchedQuery) { 16 | const filteredJobs = allJobs.filter((job) => { 17 | return job.title.toLowerCase().includes(searchedQuery.toLowerCase()) || 18 | job.description.toLowerCase().includes(searchedQuery.toLowerCase()) || 19 | job.location.toLowerCase().includes(searchedQuery.toLowerCase()) 20 | }) 21 | setFilterJobs(filteredJobs) 22 | } else { 23 | setFilterJobs(allJobs) 24 | } 25 | }, [allJobs, searchedQuery]); 26 | 27 | return ( 28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 | { 36 | filterJobs.length <= 0 ? Job not found : ( 37 |
38 |
39 | { 40 | filterJobs.map((job) => ( 41 | 47 | 48 | 49 | )) 50 | } 51 |
52 |
53 | ) 54 | } 55 |
56 |
57 | 58 | 59 |
60 | ) 61 | } 62 | 63 | export default Jobs -------------------------------------------------------------------------------- /frontend/src/components/LatestJobCards.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Badge } from './ui/badge' 3 | import { useNavigate } from 'react-router-dom' 4 | 5 | const LatestJobCards = ({job}) => { 6 | const navigate = useNavigate(); 7 | return ( 8 |
navigate(`/description/${job._id}`)} className='p-5 rounded-md shadow-xl bg-white border border-gray-100 cursor-pointer'> 9 |
10 |

{job?.company?.name}

11 |

India

12 |
13 |
14 |

{job?.title}

15 |

{job?.description}

16 |
17 |
18 | {job?.position} Positions 19 | {job?.jobType} 20 | {job?.salary}LPA 21 |
22 | 23 |
24 | ) 25 | } 26 | 27 | export default LatestJobCards -------------------------------------------------------------------------------- /frontend/src/components/LatestJobs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LatestJobCards from './LatestJobCards'; 3 | import { useSelector } from 'react-redux'; 4 | 5 | // const randomJobs = [1, 2, 3, 4, 5, 6, 7, 8]; 6 | 7 | const LatestJobs = () => { 8 | const {allJobs} = useSelector(store=>store.job); 9 | 10 | return ( 11 |
12 |

Latest & Top Job Openings

13 |
14 | { 15 | allJobs.length <= 0 ? No Job Available : allJobs?.slice(0,6).map((job) => ) 16 | } 17 |
18 |
19 | ) 20 | } 21 | 22 | export default LatestJobs -------------------------------------------------------------------------------- /frontend/src/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Navbar from './shared/Navbar' 3 | import { Avatar, AvatarImage } from './ui/avatar' 4 | import { Button } from './ui/button' 5 | import { Contact, Mail, Pen } from 'lucide-react' 6 | import { Badge } from './ui/badge' 7 | import { Label } from './ui/label' 8 | import AppliedJobTable from './AppliedJobTable' 9 | import UpdateProfileDialog from './UpdateProfileDialog' 10 | import { useSelector } from 'react-redux' 11 | import useGetAppliedJobs from '@/hooks/useGetAppliedJobs' 12 | 13 | // const skills = ["Html", "Css", "Javascript", "Reactjs"] 14 | const isResume = true; 15 | 16 | const Profile = () => { 17 | useGetAppliedJobs(); 18 | const [open, setOpen] = useState(false); 19 | const {user} = useSelector(store=>store.auth); 20 | 21 | return ( 22 |
23 | 24 |
25 |
26 |
27 | 28 | 29 | 30 |
31 |

{user?.fullname}

32 |

{user?.profile?.bio}

33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | {user?.email} 41 |
42 |
43 | 44 | {user?.phoneNumber} 45 |
46 |
47 |
48 |

Skills

49 |
50 | { 51 | user?.profile?.skills.length !== 0 ? user?.profile?.skills.map((item, index) => {item}) : NA 52 | } 53 |
54 |
55 |
56 | 57 | { 58 | isResume ? {user?.profile?.resumeOriginalName} : NA 59 | } 60 |
61 |
62 |
63 |

Applied Jobs

64 | {/* Applied Job Table */} 65 | 66 |
67 | 68 |
69 | ) 70 | } 71 | 72 | export default Profile -------------------------------------------------------------------------------- /frontend/src/components/UpdateProfileDialog.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog' 3 | import { Label } from './ui/label' 4 | import { Input } from './ui/input' 5 | import { Button } from './ui/button' 6 | import { Loader2 } from 'lucide-react' 7 | import { useDispatch, useSelector } from 'react-redux' 8 | import axios from 'axios' 9 | import { USER_API_END_POINT } from '@/utils/constant' 10 | import { setUser } from '@/redux/authSlice' 11 | import { toast } from 'sonner' 12 | 13 | const UpdateProfileDialog = ({ open, setOpen }) => { 14 | const [loading, setLoading] = useState(false); 15 | const { user } = useSelector(store => store.auth); 16 | 17 | const [input, setInput] = useState({ 18 | fullname: user?.fullname || "", 19 | email: user?.email || "", 20 | phoneNumber: user?.phoneNumber || "", 21 | bio: user?.profile?.bio || "", 22 | skills: user?.profile?.skills?.map(skill => skill) || "", 23 | file: user?.profile?.resume || "" 24 | }); 25 | const dispatch = useDispatch(); 26 | 27 | const changeEventHandler = (e) => { 28 | setInput({ ...input, [e.target.name]: e.target.value }); 29 | } 30 | 31 | const fileChangeHandler = (e) => { 32 | const file = e.target.files?.[0]; 33 | setInput({ ...input, file }) 34 | } 35 | 36 | const submitHandler = async (e) => { 37 | e.preventDefault(); 38 | const formData = new FormData(); 39 | formData.append("fullname", input.fullname); 40 | formData.append("email", input.email); 41 | formData.append("phoneNumber", input.phoneNumber); 42 | formData.append("bio", input.bio); 43 | formData.append("skills", input.skills); 44 | if (input.file) { 45 | formData.append("file", input.file); 46 | } 47 | try { 48 | setLoading(true); 49 | const res = await axios.post(`${USER_API_END_POINT}/profile/update`, formData, { 50 | headers: { 51 | 'Content-Type': 'multipart/form-data' 52 | }, 53 | withCredentials: true 54 | }); 55 | if (res.data.success) { 56 | dispatch(setUser(res.data.user)); 57 | toast.success(res.data.message); 58 | } 59 | } catch (error) { 60 | console.log(error); 61 | toast.error(error.response.data.message); 62 | } finally{ 63 | setLoading(false); 64 | } 65 | setOpen(false); 66 | console.log(input); 67 | } 68 | 69 | 70 | 71 | return ( 72 |
73 | 74 | setOpen(false)}> 75 | 76 | Update Profile 77 | 78 |
79 |
80 |
81 | 82 | 90 |
91 |
92 | 93 | 101 |
102 |
103 | 104 | 111 |
112 |
113 | 114 | 121 |
122 |
123 | 124 | 131 |
132 |
133 | 134 | 142 |
143 |
144 | 145 | { 146 | loading ? : 147 | } 148 | 149 |
150 |
151 |
152 |
153 | ) 154 | } 155 | 156 | export default UpdateProfileDialog -------------------------------------------------------------------------------- /frontend/src/components/admin/AdminJobs.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Input } from '../ui/input' 4 | import { Button } from '../ui/button' 5 | import { useNavigate } from 'react-router-dom' 6 | import { useDispatch } from 'react-redux' 7 | import AdminJobsTable from './AdminJobsTable' 8 | import useGetAllAdminJobs from '@/hooks/useGetAllAdminJobs' 9 | import { setSearchJobByText } from '@/redux/jobSlice' 10 | 11 | const AdminJobs = () => { 12 | useGetAllAdminJobs(); 13 | const [input, setInput] = useState(""); 14 | const navigate = useNavigate(); 15 | const dispatch = useDispatch(); 16 | 17 | useEffect(() => { 18 | dispatch(setSearchJobByText(input)); 19 | }, [input]); 20 | return ( 21 |
22 | 23 |
24 |
25 | setInput(e.target.value)} 29 | /> 30 | 31 |
32 | 33 |
34 |
35 | ) 36 | } 37 | 38 | export default AdminJobs -------------------------------------------------------------------------------- /frontend/src/components/admin/AdminJobsTable.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '../ui/table' 3 | import { Avatar, AvatarImage } from '../ui/avatar' 4 | import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover' 5 | import { Edit2, Eye, MoreHorizontal } from 'lucide-react' 6 | import { useSelector } from 'react-redux' 7 | import { useNavigate } from 'react-router-dom' 8 | 9 | const AdminJobsTable = () => { 10 | const {allAdminJobs, searchJobByText} = useSelector(store=>store.job); 11 | 12 | const [filterJobs, setFilterJobs] = useState(allAdminJobs); 13 | const navigate = useNavigate(); 14 | 15 | useEffect(()=>{ 16 | console.log('called'); 17 | const filteredJobs = allAdminJobs.filter((job)=>{ 18 | if(!searchJobByText){ 19 | return true; 20 | }; 21 | return job?.title?.toLowerCase().includes(searchJobByText.toLowerCase()) || job?.company?.name.toLowerCase().includes(searchJobByText.toLowerCase()); 22 | 23 | }); 24 | setFilterJobs(filteredJobs); 25 | },[allAdminJobs,searchJobByText]) 26 | return ( 27 |
28 | 29 | A list of your recent posted jobs 30 | 31 | 32 | Company Name 33 | Role 34 | Date 35 | Action 36 | 37 | 38 | 39 | { 40 | filterJobs?.map((job) => ( 41 | 42 | {job?.company?.name} 43 | {job?.title} 44 | {job?.createdAt.split("T")[0]} 45 | 46 | 47 | 48 | 49 |
navigate(`/admin/companies/${job._id}`)} className='flex items-center gap-2 w-fit cursor-pointer'> 50 | 51 | Edit 52 |
53 |
navigate(`/admin/jobs/${job._id}/applicants`)} className='flex items-center w-fit gap-2 cursor-pointer mt-2'> 54 | 55 | Applicants 56 |
57 |
58 |
59 |
60 | 61 | 62 | )) 63 | } 64 | 65 |
66 |
67 | ) 68 | } 69 | 70 | export default AdminJobsTable -------------------------------------------------------------------------------- /frontend/src/components/admin/Applicants.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import ApplicantsTable from './ApplicantsTable' 4 | import axios from 'axios'; 5 | import { APPLICATION_API_END_POINT } from '@/utils/constant'; 6 | import { useParams } from 'react-router-dom'; 7 | import { useDispatch, useSelector } from 'react-redux'; 8 | import { setAllApplicants } from '@/redux/applicationSlice'; 9 | 10 | const Applicants = () => { 11 | const params = useParams(); 12 | const dispatch = useDispatch(); 13 | const {applicants} = useSelector(store=>store.application); 14 | 15 | useEffect(() => { 16 | const fetchAllApplicants = async () => { 17 | try { 18 | const res = await axios.get(`${APPLICATION_API_END_POINT}/${params.id}/applicants`, { withCredentials: true }); 19 | dispatch(setAllApplicants(res.data.job)); 20 | } catch (error) { 21 | console.log(error); 22 | } 23 | } 24 | fetchAllApplicants(); 25 | }, []); 26 | return ( 27 |
28 | 29 |
30 |

Applicants {applicants?.applications?.length}

31 | 32 |
33 |
34 | ) 35 | } 36 | 37 | export default Applicants -------------------------------------------------------------------------------- /frontend/src/components/admin/ApplicantsTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '../ui/table' 3 | import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; 4 | import { MoreHorizontal } from 'lucide-react'; 5 | import { useSelector } from 'react-redux'; 6 | import { toast } from 'sonner'; 7 | import { APPLICATION_API_END_POINT } from '@/utils/constant'; 8 | import axios from 'axios'; 9 | 10 | const shortlistingStatus = ["Accepted", "Rejected"]; 11 | 12 | const ApplicantsTable = () => { 13 | const { applicants } = useSelector(store => store.application); 14 | 15 | const statusHandler = async (status, id) => { 16 | console.log('called'); 17 | try { 18 | axios.defaults.withCredentials = true; 19 | const res = await axios.post(`${APPLICATION_API_END_POINT}/status/${id}/update`, { status }); 20 | console.log(res); 21 | if (res.data.success) { 22 | toast.success(res.data.message); 23 | } 24 | } catch (error) { 25 | toast.error(error.response.data.message); 26 | } 27 | } 28 | 29 | return ( 30 |
31 | 32 | A list of your recent applied user 33 | 34 | 35 | FullName 36 | Email 37 | Contact 38 | Resume 39 | Date 40 | Action 41 | 42 | 43 | 44 | { 45 | applicants && applicants?.applications?.map((item) => ( 46 | 47 | {item?.applicant?.fullname} 48 | {item?.applicant?.email} 49 | {item?.applicant?.phoneNumber} 50 | 51 | { 52 | item.applicant?.profile?.resume ? {item?.applicant?.profile?.resumeOriginalName} : NA 53 | } 54 | 55 | {item?.applicant.createdAt.split("T")[0]} 56 | 57 | 58 | 59 | 60 | 61 | 62 | { 63 | shortlistingStatus.map((status, index) => { 64 | return ( 65 |
statusHandler(status, item?._id)} key={index} className='flex w-fit items-center my-2 cursor-pointer'> 66 | {status} 67 |
68 | ) 69 | }) 70 | } 71 |
72 |
73 | 74 |
75 | 76 | 77 | )) 78 | } 79 | 80 | 81 | 82 |
83 |
84 | ) 85 | } 86 | 87 | export default ApplicantsTable -------------------------------------------------------------------------------- /frontend/src/components/admin/Companies.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Input } from '../ui/input' 4 | import { Button } from '../ui/button' 5 | import CompaniesTable from './CompaniesTable' 6 | import { useNavigate } from 'react-router-dom' 7 | import useGetAllCompanies from '@/hooks/useGetAllCompanies' 8 | import { useDispatch } from 'react-redux' 9 | import { setSearchCompanyByText } from '@/redux/companySlice' 10 | 11 | const Companies = () => { 12 | useGetAllCompanies(); 13 | const [input, setInput] = useState(""); 14 | const navigate = useNavigate(); 15 | const dispatch = useDispatch(); 16 | 17 | useEffect(()=>{ 18 | dispatch(setSearchCompanyByText(input)); 19 | },[input]); 20 | return ( 21 |
22 | 23 |
24 |
25 | setInput(e.target.value)} 29 | /> 30 | 31 |
32 | 33 |
34 |
35 | ) 36 | } 37 | 38 | export default Companies -------------------------------------------------------------------------------- /frontend/src/components/admin/CompaniesTable.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '../ui/table' 3 | import { Avatar, AvatarImage } from '../ui/avatar' 4 | import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover' 5 | import { Edit2, MoreHorizontal } from 'lucide-react' 6 | import { useSelector } from 'react-redux' 7 | import { useNavigate } from 'react-router-dom' 8 | 9 | const CompaniesTable = () => { 10 | const { companies, searchCompanyByText } = useSelector(store => store.company); 11 | const [filterCompany, setFilterCompany] = useState(companies); 12 | const navigate = useNavigate(); 13 | useEffect(()=>{ 14 | const filteredCompany = companies.length >= 0 && companies.filter((company)=>{ 15 | if(!searchCompanyByText){ 16 | return true 17 | }; 18 | return company?.name?.toLowerCase().includes(searchCompanyByText.toLowerCase()); 19 | 20 | }); 21 | setFilterCompany(filteredCompany); 22 | },[companies,searchCompanyByText]) 23 | return ( 24 |
25 | 26 | A list of your recent registered companies 27 | 28 | 29 | Logo 30 | Name 31 | Date 32 | Action 33 | 34 | 35 | 36 | { 37 | filterCompany?.map((company) => ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | {company.name} 45 | {company.createdAt.split("T")[0]} 46 | 47 | 48 | 49 | 50 |
navigate(`/admin/companies/${company._id}`)} className='flex items-center gap-2 w-fit cursor-pointer'> 51 | 52 | Edit 53 |
54 |
55 |
56 |
57 | 58 | 59 | )) 60 | } 61 | 62 |
63 |
64 | ) 65 | } 66 | 67 | export default CompaniesTable -------------------------------------------------------------------------------- /frontend/src/components/admin/CompanyCreate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Label } from '../ui/label' 4 | import { Input } from '../ui/input' 5 | import { Button } from '../ui/button' 6 | import { useNavigate } from 'react-router-dom' 7 | import axios from 'axios' 8 | import { COMPANY_API_END_POINT } from '@/utils/constant' 9 | import { toast } from 'sonner' 10 | import { useDispatch } from 'react-redux' 11 | import { setSingleCompany } from '@/redux/companySlice' 12 | 13 | const CompanyCreate = () => { 14 | const navigate = useNavigate(); 15 | const [companyName, setCompanyName] = useState(); 16 | const dispatch = useDispatch(); 17 | const registerNewCompany = async () => { 18 | try { 19 | const res = await axios.post(`${COMPANY_API_END_POINT}/register`, {companyName}, { 20 | headers:{ 21 | 'Content-Type':'application/json' 22 | }, 23 | withCredentials:true 24 | }); 25 | if(res?.data?.success){ 26 | dispatch(setSingleCompany(res.data.company)); 27 | toast.success(res.data.message); 28 | const companyId = res?.data?.company?._id; 29 | navigate(`/admin/companies/${companyId}`); 30 | } 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | } 35 | return ( 36 |
37 | 38 |
39 |
40 |

Your Company Name

41 |

What would you like to give your company name? you can change this later.

42 |
43 | 44 | 45 | setCompanyName(e.target.value)} 50 | /> 51 |
52 | 53 | 54 |
55 |
56 |
57 | ) 58 | } 59 | 60 | export default CompanyCreate -------------------------------------------------------------------------------- /frontend/src/components/admin/CompanySetup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Button } from '../ui/button' 4 | import { ArrowLeft, Loader2 } from 'lucide-react' 5 | import { Label } from '../ui/label' 6 | import { Input } from '../ui/input' 7 | import axios from 'axios' 8 | import { COMPANY_API_END_POINT } from '@/utils/constant' 9 | import { useNavigate, useParams } from 'react-router-dom' 10 | import { toast } from 'sonner' 11 | import { useSelector } from 'react-redux' 12 | import useGetCompanyById from '@/hooks/useGetCompanyById' 13 | 14 | const CompanySetup = () => { 15 | const params = useParams(); 16 | useGetCompanyById(params.id); 17 | const [input, setInput] = useState({ 18 | name: "", 19 | description: "", 20 | website: "", 21 | location: "", 22 | file: null 23 | }); 24 | const {singleCompany} = useSelector(store=>store.company); 25 | const [loading, setLoading] = useState(false); 26 | const navigate = useNavigate(); 27 | 28 | const changeEventHandler = (e) => { 29 | setInput({ ...input, [e.target.name]: e.target.value }); 30 | } 31 | 32 | const changeFileHandler = (e) => { 33 | const file = e.target.files?.[0]; 34 | setInput({ ...input, file }); 35 | } 36 | 37 | const submitHandler = async (e) => { 38 | e.preventDefault(); 39 | const formData = new FormData(); 40 | formData.append("name", input.name); 41 | formData.append("description", input.description); 42 | formData.append("website", input.website); 43 | formData.append("location", input.location); 44 | if (input.file) { 45 | formData.append("file", input.file); 46 | } 47 | try { 48 | setLoading(true); 49 | const res = await axios.put(`${COMPANY_API_END_POINT}/update/${params.id}`, formData, { 50 | headers: { 51 | 'Content-Type': 'multipart/form-data' 52 | }, 53 | withCredentials: true 54 | }); 55 | if (res.data.success) { 56 | toast.success(res.data.message); 57 | navigate("/admin/companies"); 58 | } 59 | } catch (error) { 60 | console.log(error); 61 | toast.error(error.response.data.message); 62 | } finally { 63 | setLoading(false); 64 | } 65 | } 66 | 67 | useEffect(() => { 68 | setInput({ 69 | name: singleCompany.name || "", 70 | description: singleCompany.description || "", 71 | website: singleCompany.website || "", 72 | location: singleCompany.location || "", 73 | file: singleCompany.file || null 74 | }) 75 | },[singleCompany]); 76 | 77 | return ( 78 |
79 | 80 |
81 |
82 |
83 | 87 |

Company Setup

88 |
89 |
90 |
91 | 92 | 98 |
99 |
100 | 101 | 107 |
108 |
109 | 110 | 116 |
117 |
118 | 119 | 125 |
126 |
127 | 128 | 133 |
134 |
135 | { 136 | loading ? : 137 | } 138 |
139 |
140 | 141 |
142 | ) 143 | } 144 | 145 | export default CompanySetup -------------------------------------------------------------------------------- /frontend/src/components/admin/PostJob.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Label } from '../ui/label' 4 | import { Input } from '../ui/input' 5 | import { Button } from '../ui/button' 6 | import { useSelector } from 'react-redux' 7 | import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '../ui/select' 8 | import axios from 'axios' 9 | import { JOB_API_END_POINT } from '@/utils/constant' 10 | import { toast } from 'sonner' 11 | import { useNavigate } from 'react-router-dom' 12 | import { Loader2 } from 'lucide-react' 13 | 14 | const companyArray = []; 15 | 16 | const PostJob = () => { 17 | const [input, setInput] = useState({ 18 | title: "", 19 | description: "", 20 | requirements: "", 21 | salary: "", 22 | location: "", 23 | jobType: "", 24 | experience: "", 25 | position: 0, 26 | companyId: "" 27 | }); 28 | const [loading, setLoading]= useState(false); 29 | const navigate = useNavigate(); 30 | 31 | const { companies } = useSelector(store => store.company); 32 | const changeEventHandler = (e) => { 33 | setInput({ ...input, [e.target.name]: e.target.value }); 34 | }; 35 | 36 | const selectChangeHandler = (value) => { 37 | const selectedCompany = companies.find((company)=> company.name.toLowerCase() === value); 38 | setInput({...input, companyId:selectedCompany._id}); 39 | }; 40 | 41 | const submitHandler = async (e) => { 42 | e.preventDefault(); 43 | try { 44 | setLoading(true); 45 | const res = await axios.post(`${JOB_API_END_POINT}/post`, input,{ 46 | headers:{ 47 | 'Content-Type':'application/json' 48 | }, 49 | withCredentials:true 50 | }); 51 | if(res.data.success){ 52 | toast.success(res.data.message); 53 | navigate("/admin/jobs"); 54 | } 55 | } catch (error) { 56 | toast.error(error.response.data.message); 57 | } finally{ 58 | setLoading(false); 59 | } 60 | } 61 | 62 | return ( 63 |
64 | 65 |
66 |
67 |
68 |
69 | 70 | 77 |
78 |
79 | 80 | 87 |
88 |
89 | 90 | 97 |
98 |
99 | 100 | 107 |
108 |
109 | 110 | 117 |
118 |
119 | 120 | 127 |
128 |
129 | 130 | 137 |
138 |
139 | 140 | 147 |
148 | { 149 | companies.length > 0 && ( 150 | 167 | ) 168 | } 169 |
170 | { 171 | loading ? : 172 | } 173 | { 174 | companies.length === 0 &&

*Please register a company first, before posting a jobs

175 | } 176 |
177 |
178 |
179 | ) 180 | } 181 | 182 | export default PostJob -------------------------------------------------------------------------------- /frontend/src/components/admin/ProtectedRoute.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | const ProtectedRoute = ({children}) => { 6 | const {user} = useSelector(store=>store.auth); 7 | 8 | const navigate = useNavigate(); 9 | 10 | useEffect(()=>{ 11 | if(user === null || user.role !== 'recruiter'){ 12 | navigate("/"); 13 | } 14 | },[]); 15 | 16 | return ( 17 | <> 18 | {children} 19 | 20 | ) 21 | }; 22 | export default ProtectedRoute; -------------------------------------------------------------------------------- /frontend/src/components/auth/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Label } from '../ui/label' 4 | import { Input } from '../ui/input' 5 | import { RadioGroup } from '../ui/radio-group' 6 | import { Button } from '../ui/button' 7 | import { Link, useNavigate } from 'react-router-dom' 8 | import axios from 'axios' 9 | import { USER_API_END_POINT } from '@/utils/constant' 10 | import { toast } from 'sonner' 11 | import { useDispatch, useSelector } from 'react-redux' 12 | import { setLoading, setUser } from '@/redux/authSlice' 13 | import { Loader2 } from 'lucide-react' 14 | 15 | const Login = () => { 16 | const [input, setInput] = useState({ 17 | email: "", 18 | password: "", 19 | role: "", 20 | }); 21 | const { loading,user } = useSelector(store => store.auth); 22 | const navigate = useNavigate(); 23 | const dispatch = useDispatch(); 24 | 25 | const changeEventHandler = (e) => { 26 | setInput({ ...input, [e.target.name]: e.target.value }); 27 | } 28 | 29 | const submitHandler = async (e) => { 30 | e.preventDefault(); 31 | try { 32 | dispatch(setLoading(true)); 33 | const res = await axios.post(`${USER_API_END_POINT}/login`, input, { 34 | headers: { 35 | "Content-Type": "application/json" 36 | }, 37 | withCredentials: true, 38 | }); 39 | if (res.data.success) { 40 | dispatch(setUser(res.data.user)); 41 | navigate("/"); 42 | toast.success(res.data.message); 43 | } 44 | } catch (error) { 45 | console.log(error); 46 | toast.error(error.response.data.message); 47 | } finally { 48 | dispatch(setLoading(false)); 49 | } 50 | } 51 | useEffect(()=>{ 52 | if(user){ 53 | navigate("/"); 54 | } 55 | },[]) 56 | return ( 57 |
58 | 59 |
60 |
61 |

Login

62 |
63 | 64 | 71 |
72 | 73 |
74 | 75 | 82 |
83 |
84 | 85 |
86 | 94 | 95 |
96 |
97 | 105 | 106 |
107 |
108 |
109 | { 110 | loading ? : 111 | } 112 | Don't have an account? Signup 113 |
114 |
115 |
116 | ) 117 | } 118 | 119 | export default Login -------------------------------------------------------------------------------- /frontend/src/components/auth/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import Navbar from '../shared/Navbar' 3 | import { Label } from '../ui/label' 4 | import { Input } from '../ui/input' 5 | import { RadioGroup } from '../ui/radio-group' 6 | import { Button } from '../ui/button' 7 | import { Link, useNavigate } from 'react-router-dom' 8 | import axios from 'axios' 9 | import { USER_API_END_POINT } from '@/utils/constant' 10 | import { toast } from 'sonner' 11 | import { useDispatch, useSelector } from 'react-redux' 12 | import { setLoading } from '@/redux/authSlice' 13 | import { Loader2 } from 'lucide-react' 14 | 15 | const Signup = () => { 16 | 17 | const [input, setInput] = useState({ 18 | fullname: "", 19 | email: "", 20 | phoneNumber: "", 21 | password: "", 22 | role: "", 23 | file: "" 24 | }); 25 | const {loading,user} = useSelector(store=>store.auth); 26 | const dispatch = useDispatch(); 27 | const navigate = useNavigate(); 28 | 29 | const changeEventHandler = (e) => { 30 | setInput({ ...input, [e.target.name]: e.target.value }); 31 | } 32 | const changeFileHandler = (e) => { 33 | setInput({ ...input, file: e.target.files?.[0] }); 34 | } 35 | const submitHandler = async (e) => { 36 | e.preventDefault(); 37 | const formData = new FormData(); //formdata object 38 | formData.append("fullname", input.fullname); 39 | formData.append("email", input.email); 40 | formData.append("phoneNumber", input.phoneNumber); 41 | formData.append("password", input.password); 42 | formData.append("role", input.role); 43 | if (input.file) { 44 | formData.append("file", input.file); 45 | } 46 | 47 | try { 48 | dispatch(setLoading(true)); 49 | const res = await axios.post(`${USER_API_END_POINT}/register`, formData, { 50 | headers: { 'Content-Type': "multipart/form-data" }, 51 | withCredentials: true, 52 | }); 53 | if (res.data.success) { 54 | navigate("/login"); 55 | toast.success(res.data.message); 56 | } 57 | } catch (error) { 58 | console.log(error); 59 | toast.error(error.response.data.message); 60 | } finally{ 61 | dispatch(setLoading(false)); 62 | } 63 | } 64 | 65 | useEffect(()=>{ 66 | if(user){ 67 | navigate("/"); 68 | } 69 | },[]) 70 | return ( 71 |
72 | 73 |
74 |
75 |

Sign Up

76 |
77 | 78 | 85 |
86 |
87 | 88 | 95 |
96 |
97 | 98 | 105 |
106 |
107 | 108 | 115 |
116 |
117 | 118 |
119 | 127 | 128 |
129 |
130 | 138 | 139 |
140 |
141 |
142 | 143 | 149 |
150 |
151 | { 152 | loading ? : 153 | } 154 | Already have an account? Login 155 |
156 |
157 |
158 | ) 159 | } 160 | 161 | export default Signup -------------------------------------------------------------------------------- /frontend/src/components/shared/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => { 4 | return ( 5 | 27 | ); 28 | } 29 | 30 | export default Footer; -------------------------------------------------------------------------------- /frontend/src/components/shared/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover' 3 | import { Button } from '../ui/button' 4 | import { Avatar, AvatarImage } from '../ui/avatar' 5 | import { LogOut, User2 } from 'lucide-react' 6 | import { Link, useNavigate } from 'react-router-dom' 7 | import { useDispatch, useSelector } from 'react-redux' 8 | import axios from 'axios' 9 | import { USER_API_END_POINT } from '@/utils/constant' 10 | import { setUser } from '@/redux/authSlice' 11 | import { toast } from 'sonner' 12 | 13 | const Navbar = () => { 14 | const { user } = useSelector(store => store.auth); 15 | const dispatch = useDispatch(); 16 | const navigate = useNavigate(); 17 | 18 | const logoutHandler = async () => { 19 | try { 20 | const res = await axios.get(`${USER_API_END_POINT}/logout`, { withCredentials: true }); 21 | if (res.data.success) { 22 | dispatch(setUser(null)); 23 | navigate("/"); 24 | toast.success(res.data.message); 25 | } 26 | } catch (error) { 27 | console.log(error); 28 | toast.error(error.response.data.message); 29 | } 30 | } 31 | return ( 32 |
33 |
34 |
35 |

JobPortal

36 |
37 |
38 |
    39 | { 40 | user && user.role === 'recruiter' ? ( 41 | <> 42 |
  • Companies
  • 43 |
  • Jobs
  • 44 | 45 | ) : ( 46 | <> 47 |
  • Home
  • 48 |
  • Jobs
  • 49 |
  • Browse
  • 50 | 51 | ) 52 | } 53 | 54 | 55 |
56 | { 57 | !user ? ( 58 |
59 | 60 | 61 |
62 | ) : ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 |
76 |

{user?.fullname}

77 |

{user?.profile?.bio}

78 |
79 |
80 |
81 | { 82 | user && user.role === 'student' && ( 83 |
84 | 85 | 86 |
87 | ) 88 | } 89 | 90 |
91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | ) 99 | } 100 | 101 |
102 |
103 | 104 |
105 | ) 106 | } 107 | 108 | export default Navbar -------------------------------------------------------------------------------- /frontend/src/components/ui/avatar.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Avatar = React.forwardRef(({ className, ...props }, ref) => ( 7 | 11 | )) 12 | Avatar.displayName = AvatarPrimitive.Root.displayName 13 | 14 | const AvatarImage = React.forwardRef(({ className, ...props }, ref) => ( 15 | 19 | )) 20 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 21 | 22 | const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => ( 23 | 30 | )) 31 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 32 | 33 | export { Avatar, AvatarImage, AvatarFallback } 34 | -------------------------------------------------------------------------------- /frontend/src/components/ui/badge.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | function Badge({ 27 | className, 28 | variant, 29 | ...props 30 | }) { 31 | return (
); 32 | } 33 | 34 | export { Badge, badgeVariants } 35 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => { 37 | const Comp = asChild ? Slot : "button" 38 | return ( 39 | () 43 | ); 44 | }) 45 | Button.displayName = "Button" 46 | 47 | export { Button, buttonVariants } 48 | -------------------------------------------------------------------------------- /frontend/src/components/ui/carousel.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import useEmblaCarousel from "embla-carousel-react"; 3 | import { ArrowLeft, ArrowRight } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | import { Button } from "@/components/ui/button" 7 | 8 | const CarouselContext = React.createContext(null) 9 | 10 | function useCarousel() { 11 | const context = React.useContext(CarouselContext) 12 | 13 | if (!context) { 14 | throw new Error("useCarousel must be used within a ") 15 | } 16 | 17 | return context 18 | } 19 | 20 | const Carousel = React.forwardRef(( 21 | { 22 | orientation = "horizontal", 23 | opts, 24 | setApi, 25 | plugins, 26 | className, 27 | children, 28 | ...props 29 | }, 30 | ref 31 | ) => { 32 | const [carouselRef, api] = useEmblaCarousel({ 33 | ...opts, 34 | axis: orientation === "horizontal" ? "x" : "y", 35 | }, plugins) 36 | const [canScrollPrev, setCanScrollPrev] = React.useState(false) 37 | const [canScrollNext, setCanScrollNext] = React.useState(false) 38 | 39 | const onSelect = React.useCallback((api) => { 40 | if (!api) { 41 | return 42 | } 43 | 44 | setCanScrollPrev(api.canScrollPrev()) 45 | setCanScrollNext(api.canScrollNext()) 46 | }, []) 47 | 48 | const scrollPrev = React.useCallback(() => { 49 | api?.scrollPrev() 50 | }, [api]) 51 | 52 | const scrollNext = React.useCallback(() => { 53 | api?.scrollNext() 54 | }, [api]) 55 | 56 | const handleKeyDown = React.useCallback((event) => { 57 | if (event.key === "ArrowLeft") { 58 | event.preventDefault() 59 | scrollPrev() 60 | } else if (event.key === "ArrowRight") { 61 | event.preventDefault() 62 | scrollNext() 63 | } 64 | }, [scrollPrev, scrollNext]) 65 | 66 | React.useEffect(() => { 67 | if (!api || !setApi) { 68 | return 69 | } 70 | 71 | setApi(api) 72 | }, [api, setApi]) 73 | 74 | React.useEffect(() => { 75 | if (!api) { 76 | return 77 | } 78 | 79 | onSelect(api) 80 | api.on("reInit", onSelect) 81 | api.on("select", onSelect) 82 | 83 | return () => { 84 | api?.off("select", onSelect) 85 | }; 86 | }, [api, onSelect]) 87 | 88 | return ( 89 | ( 101 |
108 | {children} 109 |
110 |
) 111 | ); 112 | }) 113 | Carousel.displayName = "Carousel" 114 | 115 | const CarouselContent = React.forwardRef(({ className, ...props }, ref) => { 116 | const { carouselRef, orientation } = useCarousel() 117 | 118 | return ( 119 | (
120 |
128 |
) 129 | ); 130 | }) 131 | CarouselContent.displayName = "CarouselContent" 132 | 133 | const CarouselItem = React.forwardRef(({ className, ...props }, ref) => { 134 | const { orientation } = useCarousel() 135 | 136 | return ( 137 | (
) 147 | ); 148 | }) 149 | CarouselItem.displayName = "CarouselItem" 150 | 151 | const CarouselPrevious = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => { 152 | const { orientation, scrollPrev, canScrollPrev } = useCarousel() 153 | 154 | return ( 155 | () 168 | ); 169 | }) 170 | CarouselPrevious.displayName = "CarouselPrevious" 171 | 172 | const CarouselNext = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => { 173 | const { orientation, scrollNext, canScrollNext } = useCarousel() 174 | 175 | return ( 176 | () 189 | ); 190 | }) 191 | CarouselNext.displayName = "CarouselNext" 192 | 193 | export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }; 194 | -------------------------------------------------------------------------------- /frontend/src/components/ui/dialog.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DialogPrimitive from "@radix-ui/react-dialog" 3 | import { X } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Dialog = DialogPrimitive.Root 8 | 9 | const DialogTrigger = DialogPrimitive.Trigger 10 | 11 | const DialogPortal = DialogPrimitive.Portal 12 | 13 | const DialogClose = DialogPrimitive.Close 14 | 15 | const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( 16 | 23 | )) 24 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 25 | 26 | const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => ( 27 | 28 | 29 | 36 | {children} 37 | 39 | 40 | Close 41 | 42 | 43 | 44 | )) 45 | DialogContent.displayName = DialogPrimitive.Content.displayName 46 | 47 | const DialogHeader = ({ 48 | className, 49 | ...props 50 | }) => ( 51 |
54 | ) 55 | DialogHeader.displayName = "DialogHeader" 56 | 57 | const DialogFooter = ({ 58 | className, 59 | ...props 60 | }) => ( 61 |
64 | ) 65 | DialogFooter.displayName = "DialogFooter" 66 | 67 | const DialogTitle = React.forwardRef(({ className, ...props }, ref) => ( 68 | 72 | )) 73 | DialogTitle.displayName = DialogPrimitive.Title.displayName 74 | 75 | const DialogDescription = React.forwardRef(({ className, ...props }, ref) => ( 76 | 80 | )) 81 | DialogDescription.displayName = DialogPrimitive.Description.displayName 82 | 83 | export { 84 | Dialog, 85 | DialogPortal, 86 | DialogOverlay, 87 | DialogClose, 88 | DialogTrigger, 89 | DialogContent, 90 | DialogHeader, 91 | DialogFooter, 92 | DialogTitle, 93 | DialogDescription, 94 | } 95 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef(({ className, type, ...props }, ref) => { 6 | return ( 7 | () 15 | ); 16 | }) 17 | Input.displayName = "Input" 18 | 19 | export { Input } 20 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef(({ className, ...props }, ref) => ( 12 | 13 | )) 14 | Label.displayName = LabelPrimitive.Root.displayName 15 | 16 | export { Label } 17 | -------------------------------------------------------------------------------- /frontend/src/components/ui/popover.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as PopoverPrimitive from "@radix-ui/react-popover" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Popover = PopoverPrimitive.Root 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger 9 | 10 | const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 11 | 12 | 21 | 22 | )) 23 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 24 | 25 | export { Popover, PopoverTrigger, PopoverContent } 26 | -------------------------------------------------------------------------------- /frontend/src/components/ui/radio-group.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 3 | import { Circle } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const RadioGroup = React.forwardRef(({ className, ...props }, ref) => { 8 | return (); 9 | }) 10 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName 11 | 12 | const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => { 13 | return ( 14 | ( 21 | 22 | 23 | 24 | ) 25 | ); 26 | }) 27 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName 28 | 29 | export { RadioGroup, RadioGroupItem } 30 | -------------------------------------------------------------------------------- /frontend/src/components/ui/select.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SelectPrimitive from "@radix-ui/react-select" 3 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Select = SelectPrimitive.Root 8 | 9 | const SelectGroup = SelectPrimitive.Group 10 | 11 | const SelectValue = SelectPrimitive.Value 12 | 13 | const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => ( 14 | span]:line-clamp-1", 18 | className 19 | )} 20 | {...props}> 21 | {children} 22 | 23 | 24 | 25 | 26 | )) 27 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 28 | 29 | const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => ( 30 | 34 | 35 | 36 | )) 37 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 38 | 39 | const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => ( 40 | 44 | 45 | 46 | )) 47 | SelectScrollDownButton.displayName = 48 | SelectPrimitive.ScrollDownButton.displayName 49 | 50 | const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => ( 51 | 52 | 62 | 63 | 66 | {children} 67 | 68 | 69 | 70 | 71 | )) 72 | SelectContent.displayName = SelectPrimitive.Content.displayName 73 | 74 | const SelectLabel = React.forwardRef(({ className, ...props }, ref) => ( 75 | 79 | )) 80 | SelectLabel.displayName = SelectPrimitive.Label.displayName 81 | 82 | const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => ( 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {children} 97 | 98 | )) 99 | SelectItem.displayName = SelectPrimitive.Item.displayName 100 | 101 | const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => ( 102 | 106 | )) 107 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 108 | 109 | export { 110 | Select, 111 | SelectGroup, 112 | SelectValue, 113 | SelectTrigger, 114 | SelectContent, 115 | SelectLabel, 116 | SelectItem, 117 | SelectSeparator, 118 | SelectScrollUpButton, 119 | SelectScrollDownButton, 120 | } 121 | -------------------------------------------------------------------------------- /frontend/src/components/ui/sonner.jsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes" 2 | import { Toaster as Sonner } from "sonner" 3 | 4 | const Toaster = ({ 5 | ...props 6 | }) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | () 25 | ); 26 | } 27 | 28 | export { Toaster } 29 | -------------------------------------------------------------------------------- /frontend/src/components/ui/table.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef(({ className, ...props }, ref) => ( 6 |
7 | 11 | 12 | )) 13 | Table.displayName = "Table" 14 | 15 | const TableHeader = React.forwardRef(({ className, ...props }, ref) => ( 16 | 17 | )) 18 | TableHeader.displayName = "TableHeader" 19 | 20 | const TableBody = React.forwardRef(({ className, ...props }, ref) => ( 21 | 25 | )) 26 | TableBody.displayName = "TableBody" 27 | 28 | const TableFooter = React.forwardRef(({ className, ...props }, ref) => ( 29 | tr]:last:border-b-0", className)} 32 | {...props} /> 33 | )) 34 | TableFooter.displayName = "TableFooter" 35 | 36 | const TableRow = React.forwardRef(({ className, ...props }, ref) => ( 37 | 44 | )) 45 | TableRow.displayName = "TableRow" 46 | 47 | const TableHead = React.forwardRef(({ className, ...props }, ref) => ( 48 |
55 | )) 56 | TableHead.displayName = "TableHead" 57 | 58 | const TableCell = React.forwardRef(({ className, ...props }, ref) => ( 59 | 63 | )) 64 | TableCell.displayName = "TableCell" 65 | 66 | const TableCaption = React.forwardRef(({ className, ...props }, ref) => ( 67 |
71 | )) 72 | TableCaption.displayName = "TableCaption" 73 | 74 | export { 75 | Table, 76 | TableHeader, 77 | TableBody, 78 | TableFooter, 79 | TableHead, 80 | TableRow, 81 | TableCell, 82 | TableCaption, 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/hooks/useGetAllAdminJobs.jsx: -------------------------------------------------------------------------------- 1 | import { setAllAdminJobs } from '@/redux/jobSlice' 2 | import { JOB_API_END_POINT } from '@/utils/constant' 3 | import axios from 'axios' 4 | import { useEffect } from 'react' 5 | import { useDispatch } from 'react-redux' 6 | 7 | const useGetAllAdminJobs = () => { 8 | const dispatch = useDispatch(); 9 | useEffect(()=>{ 10 | const fetchAllAdminJobs = async () => { 11 | try { 12 | const res = await axios.get(`${JOB_API_END_POINT}/getadminjobs`,{withCredentials:true}); 13 | if(res.data.success){ 14 | dispatch(setAllAdminJobs(res.data.jobs)); 15 | } 16 | } catch (error) { 17 | console.log(error); 18 | } 19 | } 20 | fetchAllAdminJobs(); 21 | },[]) 22 | } 23 | 24 | export default useGetAllAdminJobs -------------------------------------------------------------------------------- /frontend/src/hooks/useGetAllCompanies.jsx: -------------------------------------------------------------------------------- 1 | import { setCompanies} from '@/redux/companySlice' 2 | import { COMPANY_API_END_POINT} from '@/utils/constant' 3 | import axios from 'axios' 4 | import { useEffect } from 'react' 5 | import { useDispatch } from 'react-redux' 6 | 7 | const useGetAllCompanies = () => { 8 | const dispatch = useDispatch(); 9 | useEffect(()=>{ 10 | const fetchCompanies = async () => { 11 | try { 12 | const res = await axios.get(`${COMPANY_API_END_POINT}/get`,{withCredentials:true}); 13 | console.log('called'); 14 | if(res.data.success){ 15 | dispatch(setCompanies(res.data.companies)); 16 | } 17 | } catch (error) { 18 | console.log(error); 19 | } 20 | } 21 | fetchCompanies(); 22 | },[]) 23 | } 24 | 25 | export default useGetAllCompanies -------------------------------------------------------------------------------- /frontend/src/hooks/useGetAllJobs.jsx: -------------------------------------------------------------------------------- 1 | import { setAllJobs } from '@/redux/jobSlice' 2 | import { JOB_API_END_POINT } from '@/utils/constant' 3 | import axios from 'axios' 4 | import { useEffect } from 'react' 5 | import { useDispatch, useSelector } from 'react-redux' 6 | 7 | const useGetAllJobs = () => { 8 | const dispatch = useDispatch(); 9 | const {searchedQuery} = useSelector(store=>store.job); 10 | useEffect(()=>{ 11 | const fetchAllJobs = async () => { 12 | try { 13 | const res = await axios.get(`${JOB_API_END_POINT}/get?keyword=${searchedQuery}`,{withCredentials:true}); 14 | if(res.data.success){ 15 | dispatch(setAllJobs(res.data.jobs)); 16 | } 17 | } catch (error) { 18 | console.log(error); 19 | } 20 | } 21 | fetchAllJobs(); 22 | },[]) 23 | } 24 | 25 | export default useGetAllJobs -------------------------------------------------------------------------------- /frontend/src/hooks/useGetAppliedJobs.jsx: -------------------------------------------------------------------------------- 1 | import { setAllAppliedJobs } from "@/redux/jobSlice"; 2 | import { APPLICATION_API_END_POINT } from "@/utils/constant"; 3 | import axios from "axios" 4 | import { useEffect } from "react" 5 | import { useDispatch } from "react-redux" 6 | 7 | const useGetAppliedJobs = () => { 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(()=>{ 11 | const fetchAppliedJobs = async () => { 12 | try { 13 | const res = await axios.get(`${APPLICATION_API_END_POINT}/get`, {withCredentials:true}); 14 | console.log(res.data); 15 | if(res.data.success){ 16 | dispatch(setAllAppliedJobs(res.data.application)); 17 | } 18 | } catch (error) { 19 | console.log(error); 20 | } 21 | } 22 | fetchAppliedJobs(); 23 | },[]) 24 | }; 25 | export default useGetAppliedJobs; -------------------------------------------------------------------------------- /frontend/src/hooks/useGetCompanyById.jsx: -------------------------------------------------------------------------------- 1 | import { setSingleCompany } from '@/redux/companySlice' 2 | import { setAllJobs } from '@/redux/jobSlice' 3 | import { COMPANY_API_END_POINT, JOB_API_END_POINT } from '@/utils/constant' 4 | import axios from 'axios' 5 | import { useEffect } from 'react' 6 | import { useDispatch } from 'react-redux' 7 | 8 | const useGetCompanyById = (companyId) => { 9 | const dispatch = useDispatch(); 10 | useEffect(()=>{ 11 | const fetchSingleCompany = async () => { 12 | try { 13 | const res = await axios.get(`${COMPANY_API_END_POINT}/get/${companyId}`,{withCredentials:true}); 14 | console.log(res.data.company); 15 | if(res.data.success){ 16 | dispatch(setSingleCompany(res.data.company)); 17 | } 18 | } catch (error) { 19 | console.log(error); 20 | } 21 | } 22 | fetchSingleCompany(); 23 | },[companyId, dispatch]) 24 | } 25 | 26 | export default useGetCompanyById -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /frontend/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | import { Toaster } from './components/ui/sonner.jsx' 6 | import { Provider } from 'react-redux' 7 | import store from './redux/store.js' 8 | import { persistStore } from 'redux-persist' 9 | import { PersistGate } from 'redux-persist/integration/react' 10 | 11 | const persistor = persistStore(store); 12 | 13 | ReactDOM.createRoot(document.getElementById('root')).render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | , 22 | ) 23 | -------------------------------------------------------------------------------- /frontend/src/redux/applicationSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const applicationSlice = createSlice({ 4 | name:'application', 5 | initialState:{ 6 | applicants:null, 7 | }, 8 | reducers:{ 9 | setAllApplicants:(state,action) => { 10 | state.applicants = action.payload; 11 | } 12 | } 13 | }); 14 | export const {setAllApplicants} = applicationSlice.actions; 15 | export default applicationSlice.reducer; -------------------------------------------------------------------------------- /frontend/src/redux/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const authSlice = createSlice({ 4 | name:"auth", 5 | initialState:{ 6 | loading:false, 7 | user:null 8 | }, 9 | reducers:{ 10 | // actions 11 | setLoading:(state, action) => { 12 | state.loading = action.payload; 13 | }, 14 | setUser:(state, action) => { 15 | state.user = action.payload; 16 | } 17 | } 18 | }); 19 | export const {setLoading, setUser} = authSlice.actions; 20 | export default authSlice.reducer; -------------------------------------------------------------------------------- /frontend/src/redux/companySlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const companySlice = createSlice({ 4 | name:"company", 5 | initialState:{ 6 | singleCompany:null, 7 | companies:[], 8 | searchCompanyByText:"", 9 | }, 10 | reducers:{ 11 | // actions 12 | setSingleCompany:(state,action) => { 13 | state.singleCompany = action.payload; 14 | }, 15 | setCompanies:(state,action) => { 16 | state.companies = action.payload; 17 | }, 18 | setSearchCompanyByText:(state,action) => { 19 | state.searchCompanyByText = action.payload; 20 | } 21 | } 22 | }); 23 | export const {setSingleCompany, setCompanies,setSearchCompanyByText} = companySlice.actions; 24 | export default companySlice.reducer; -------------------------------------------------------------------------------- /frontend/src/redux/jobSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const jobSlice = createSlice({ 4 | name:"job", 5 | initialState:{ 6 | allJobs:[], 7 | allAdminJobs:[], 8 | singleJob:null, 9 | searchJobByText:"", 10 | allAppliedJobs:[], 11 | searchedQuery:"", 12 | }, 13 | reducers:{ 14 | // actions 15 | setAllJobs:(state,action) => { 16 | state.allJobs = action.payload; 17 | }, 18 | setSingleJob:(state,action) => { 19 | state.singleJob = action.payload; 20 | }, 21 | setAllAdminJobs:(state,action) => { 22 | state.allAdminJobs = action.payload; 23 | }, 24 | setSearchJobByText:(state,action) => { 25 | state.searchJobByText = action.payload; 26 | }, 27 | setAllAppliedJobs:(state,action) => { 28 | state.allAppliedJobs = action.payload; 29 | }, 30 | setSearchedQuery:(state,action) => { 31 | state.searchedQuery = action.payload; 32 | } 33 | } 34 | }); 35 | export const { 36 | setAllJobs, 37 | setSingleJob, 38 | setAllAdminJobs, 39 | setSearchJobByText, 40 | setAllAppliedJobs, 41 | setSearchedQuery 42 | } = jobSlice.actions; 43 | export default jobSlice.reducer; -------------------------------------------------------------------------------- /frontend/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, configureStore } from "@reduxjs/toolkit"; 2 | import authSlice from "./authSlice"; 3 | import jobSlice from "./jobSlice"; 4 | import { 5 | persistStore, 6 | persistReducer, 7 | FLUSH, 8 | REHYDRATE, 9 | PAUSE, 10 | PERSIST, 11 | PURGE, 12 | REGISTER, 13 | } from 'redux-persist' 14 | import storage from 'redux-persist/lib/storage' 15 | import companySlice from "./companySlice"; 16 | import applicationSlice from "./applicationSlice"; 17 | 18 | const persistConfig = { 19 | key: 'root', 20 | version: 1, 21 | storage, 22 | } 23 | 24 | const rootReducer = combineReducers({ 25 | auth:authSlice, 26 | job:jobSlice, 27 | company:companySlice, 28 | application:applicationSlice 29 | }) 30 | 31 | const persistedReducer = persistReducer(persistConfig, rootReducer) 32 | 33 | 34 | const store = configureStore({ 35 | reducer: persistedReducer, 36 | middleware: (getDefaultMiddleware) => 37 | getDefaultMiddleware({ 38 | serializableCheck: { 39 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 40 | }, 41 | }), 42 | }); 43 | export default store; -------------------------------------------------------------------------------- /frontend/src/utils/constant.js: -------------------------------------------------------------------------------- 1 | export const USER_API_END_POINT="http://localhost:8000/api/v1/user"; 2 | export const JOB_API_END_POINT="http://localhost:8000/api/v1/job"; 3 | export const APPLICATION_API_END_POINT="http://localhost:8000/api/v1/application"; 4 | export const COMPANY_API_END_POINT="http://localhost:8000/api/v1/company"; -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{js,jsx}', 6 | './components/**/*.{js,jsx}', 7 | './app/**/*.{js,jsx}', 8 | './src/**/*.{js,jsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 26 | primary: { 27 | DEFAULT: "hsl(var(--primary))", 28 | foreground: "hsl(var(--primary-foreground))", 29 | }, 30 | secondary: { 31 | DEFAULT: "hsl(var(--secondary))", 32 | foreground: "hsl(var(--secondary-foreground))", 33 | }, 34 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { height: "0" }, 63 | to: { height: "var(--radix-accordion-content-height)" }, 64 | }, 65 | "accordion-up": { 66 | from: { height: "var(--radix-accordion-content-height)" }, 67 | to: { height: "0" }, 68 | }, 69 | }, 70 | animation: { 71 | "accordion-down": "accordion-down 0.2s ease-out", 72 | "accordion-up": "accordion-up 0.2s ease-out", 73 | }, 74 | }, 75 | }, 76 | plugins: [require("tailwindcss-animate")], 77 | } -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import react from "@vitejs/plugin-react" 3 | import { defineConfig } from "vite" 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }) 13 | --------------------------------------------------------------------------------