├── 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 | searchJobHandler(cat)} variant="outline" className="rounded-full">{cat}
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 | {item}
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 |
33 |
34 |
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 |
28 |
29 |
30 |
31 |
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 | navigate(`/description/${job?._id}`)} variant="outline">Details
49 | Save For Later
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 |
68 | {isApplied ? 'Already Applied' : 'Apply Now'}
69 |
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 |
setOpen(true)} className="text-right" variant="outline">
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 |
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 |
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 | navigate("/admin/jobs/create")}>New Jobs
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 | navigate("/admin/companies/create")}>New Company
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 |
Company Name
45 |
setCompanyName(e.target.value)}
50 | />
51 |
52 | navigate("/admin/companies")}>Cancel
53 | Continue
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 |
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 |
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 |
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 |
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 |
6 |
7 |
8 |
9 |
Job Hunt
10 |
© 2024 Your Company. All rights reserved.
11 |
12 |
13 |
24 |
25 |
26 |
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 | Login
60 | Signup
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 | View Profile
86 |
87 | )
88 | }
89 |
90 |
91 |
92 | Logout
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 | ()
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 | (
165 |
166 | Previous slide
167 | )
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 | (
186 |
187 | Next slide
188 | )
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 |
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 |
--------------------------------------------------------------------------------