├── Backend
├── README.md
├── .dockerignore
├── .env
├── testEnv.txt
├── src
│ ├── index.js
│ ├── utils
│ │ ├── ApiResponse.js
│ │ └── ApiError.js
│ ├── models
│ │ ├── skill.model.js
│ │ ├── project.model.js
│ │ ├── experience.model.js
│ │ ├── education.model.js
│ │ ├── user.model.js
│ │ └── resume.model.js
│ ├── routes
│ │ ├── user.routes.js
│ │ └── resume.routes.js
│ ├── db
│ │ └── index.js
│ ├── app.js
│ ├── middleware
│ │ └── auth.js
│ └── controller
│ │ ├── user.controller.js
│ │ └── resume.controller.js
├── Dockerfile
├── docker-compose.yml
└── package.json
├── Frontend
├── src
│ ├── pages
│ │ ├── auth
│ │ │ ├── index.js
│ │ │ ├── Sign-In
│ │ │ │ └── SignInPage.jsx
│ │ │ └── customAuth
│ │ │ │ └── AuthPage.jsx
│ │ ├── dashboard
│ │ │ ├── edit-resume
│ │ │ │ ├── components
│ │ │ │ │ ├── preview-components
│ │ │ │ │ │ ├── SummaryPreview.jsx
│ │ │ │ │ │ ├── SkillsPreview.jsx
│ │ │ │ │ │ ├── PersonalDeatailPreview.jsx
│ │ │ │ │ │ ├── ProjectPreview.jsx
│ │ │ │ │ │ ├── EducationalPreview.jsx
│ │ │ │ │ │ └── ExperiencePreview.jsx
│ │ │ │ │ ├── PreviewPage.jsx
│ │ │ │ │ ├── ThemeColor.jsx
│ │ │ │ │ ├── ResumeForm.jsx
│ │ │ │ │ └── form-components
│ │ │ │ │ │ ├── Skills.jsx
│ │ │ │ │ │ ├── PersonalDetails.jsx
│ │ │ │ │ │ ├── Project.jsx
│ │ │ │ │ │ ├── Education.jsx
│ │ │ │ │ │ ├── Summary.jsx
│ │ │ │ │ │ └── Experience.jsx
│ │ │ │ └── [resume_id]
│ │ │ │ │ └── EditResume.jsx
│ │ │ ├── Dashboard.jsx
│ │ │ ├── view-resume
│ │ │ │ └── [resume_id]
│ │ │ │ │ └── ViewResume.jsx
│ │ │ └── components
│ │ │ │ ├── AddResume.jsx
│ │ │ │ └── ResumeCard.jsx
│ │ └── home
│ │ │ └── HomePage.jsx
│ ├── assets
│ │ ├── heroSnapshot.png
│ │ └── react.svg
│ ├── lib
│ │ └── utils.js
│ ├── config
│ │ └── config.js
│ ├── features
│ │ ├── user
│ │ │ └── userFeatures.js
│ │ └── resume
│ │ │ └── resumeFeatures.js
│ ├── store
│ │ └── store.js
│ ├── components
│ │ ├── ui
│ │ │ ├── textarea.jsx
│ │ │ ├── input.jsx
│ │ │ ├── sonner.jsx
│ │ │ ├── popover.jsx
│ │ │ ├── button.jsx
│ │ │ ├── alert-dialog.jsx
│ │ │ └── dialog.jsx
│ │ └── custom
│ │ │ ├── Header.jsx
│ │ │ ├── SimpeRichTextEditor.jsx
│ │ │ └── RichTextEditor.jsx
│ ├── Services
│ │ ├── AiModel.js
│ │ ├── login.js
│ │ ├── GlobalApi.js
│ │ └── resumeAPI.js
│ ├── App.jsx
│ ├── main.jsx
│ └── index.css
├── .env.local
├── postcss.config.js
├── envTest.txt
├── jsconfig.json
├── vite.config.js
├── components.json
├── index.html
├── README.md
├── public
│ ├── logo.svg
│ └── vite.svg
├── .eslintrc.cjs
├── package.json
├── tailwind.config.js
└── data
│ └── dummy.js
└── README.md
/Backend/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shauryaverma03/AI-Resume-Builder/HEAD/Backend/README.md
--------------------------------------------------------------------------------
/Backend/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | npm-debug.log
4 | .DS_Store
5 | .env
6 |
--------------------------------------------------------------------------------
/Frontend/src/pages/auth/index.js:
--------------------------------------------------------------------------------
1 | import SignInPage from "./Sign-In/SignInPage";
2 |
3 | export { SignInPage}
4 |
--------------------------------------------------------------------------------
/Frontend/.env.local:
--------------------------------------------------------------------------------
1 | VITE_GEMENI_API_KEY=AIzaSyA8g7EEqOIdv5Q4qQLZNh7aUiF1GvIee1c
2 | VITE_APP_URL=http://localhost:5001/
--------------------------------------------------------------------------------
/Frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/Frontend/src/assets/heroSnapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shauryaverma03/AI-Resume-Builder/HEAD/Frontend/src/assets/heroSnapshot.png
--------------------------------------------------------------------------------
/Frontend/envTest.txt:
--------------------------------------------------------------------------------
1 | Rename This File to .env.local
2 | VITE_GEMENI_API_KEY={Create Your Own Gemeni API Key}
3 | VITE_APP_URL=http://localhost:5001/
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Backend/.env:
--------------------------------------------------------------------------------
1 | MONGODB_URI=mongodb://127.0.0.1:27017/ai-resume-builder
2 | PORT=5001
3 | JWT_SECRET_KEY=your-secret-key
4 | JWT_SECRET_EXPIRES_IN="1d"
5 | NODE_ENV=Dev
6 | ALLOWED_SITE=http://localhost:5173
--------------------------------------------------------------------------------
/Frontend/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // ...
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": [
7 | "./src/*"
8 | ]
9 | }
10 | // ...
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/SummaryPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function SummeryPreview({resumeInfo}) {
4 | return (
5 |
6 | {resumeInfo?.summary}
7 |
8 | )
9 | }
10 |
11 | export default SummeryPreview
--------------------------------------------------------------------------------
/Backend/testEnv.txt:
--------------------------------------------------------------------------------
1 | Rename this file to .env
2 |
3 | MONGODB_URI={Create Your MongoDB URI And Use it You can use this if you are using Docker mongodb://localhost:27017/}
4 | PORT=5001
5 | JWT_SECRET_KEY={Some Random String for Authentication}
6 | JWT_SECRET_EXPIRES_IN="1d"
7 | NODE_ENV=Dev
8 | ALLOWED_SITE=http://localhost:5173
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Frontend/src/pages/auth/Sign-In/SignInPage.jsx:
--------------------------------------------------------------------------------
1 | import { SignIn } from '@clerk/clerk-react'
2 | import React from 'react'
3 |
4 | function SignInPage() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
12 | export default SignInPage
--------------------------------------------------------------------------------
/Backend/src/index.js:
--------------------------------------------------------------------------------
1 | import app from "./app.js";
2 | import { connectDB } from "./db/index.js";
3 | import { config } from "dotenv";
4 | config();
5 |
6 | connectDB().then(() => {
7 | app.listen(process.env.PORT, () => {
8 | console.log("Server is running on http://localhost:" + process.env.PORT);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/Frontend/src/config/config.js:
--------------------------------------------------------------------------------
1 | const AUTH_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
2 | const API_KEY = import.meta.env.VITE_STRAPI_API_KEY;
3 | const GEMENI_API_KEY = import.meta.env.VITE_GEMENI_API_KEY;
4 | const VITE_APP_URL = import.meta.env.VITE_APP_URL;
5 |
6 | export { AUTH_KEY, API_KEY, GEMENI_API_KEY,VITE_APP_URL };
7 |
--------------------------------------------------------------------------------
/Backend/src/utils/ApiResponse.js:
--------------------------------------------------------------------------------
1 | class ApiResponse {
2 | constructor(statusCode, data, message = "Something Went wrong") {
3 | this.statusCode = statusCode;
4 | this.data = data;
5 | this.message = message;
6 | this.success = statusCode < 400;
7 | }
8 | }
9 |
10 | export { ApiResponse };
11 |
--------------------------------------------------------------------------------
/Backend/src/models/skill.model.js:
--------------------------------------------------------------------------------
1 | // models/Skill.js
2 | import mongoose from 'mongoose';
3 |
4 | const { Schema } = mongoose;
5 |
6 | const skillSchema = new Schema({
7 | name: {
8 | type: String
9 | },
10 | rating: {
11 | type: Number
12 | }
13 | }, { _id: false });
14 |
15 | export default skillSchema;
16 |
--------------------------------------------------------------------------------
/Backend/src/models/project.model.js:
--------------------------------------------------------------------------------
1 | // models/Project.js
2 | import mongoose from 'mongoose';
3 |
4 | const { Schema } = mongoose;
5 |
6 | const projectSchema = new Schema({
7 | projectName: {
8 | type: String
9 | },
10 | techStack: {
11 | type: [String]
12 | },
13 | projectSummary: {
14 | type: String
15 | }
16 | }, { _id: false });
17 |
18 | export default projectSchema;
19 |
--------------------------------------------------------------------------------
/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": "neutral",
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 | AI Resume Builder
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/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/src/features/user/userFeatures.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | userData: "",
5 | };
6 |
7 | export const userSlice = createSlice({
8 | name: "editUser",
9 | initialState,
10 | reducers: {
11 | addUserData: (state, action) => {
12 | state.userData = action.payload;
13 | },
14 | },
15 | });
16 |
17 | export const { addUserData } = userSlice.actions;
18 |
19 | export default userSlice.reducer;
20 |
--------------------------------------------------------------------------------
/Frontend/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import resumeReducers from "../features/resume/resumeFeatures";
3 | import userReducers from "../features/user/userFeatures";
4 |
5 | export const resumeStore = configureStore({
6 | reducer: {
7 | editResume: resumeReducers,
8 | editUser: userReducers,
9 | },
10 | });
11 |
12 | export const userStore = configureStore({
13 | reducer: {
14 | editUser: userReducers,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/Backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the same Node.js version as your local machine
2 | FROM node:20-bullseye
3 |
4 | # Set working directory
5 | WORKDIR /app
6 |
7 | # Copy only package.json and package-lock.json first (for better caching)
8 | COPY package*.json ./
9 |
10 | # Install dependencies
11 | RUN npm install
12 |
13 | # Now copy the rest of the application files
14 | COPY . .
15 |
16 | # Expose port
17 | EXPOSE 5001
18 |
19 | # Start the server
20 | CMD ["node", "src/index.js"]
21 |
--------------------------------------------------------------------------------
/Backend/src/routes/user.routes.js:
--------------------------------------------------------------------------------
1 | import {
2 | start,
3 | loginUser,
4 | logoutUser,
5 | registerUser,
6 | } from "../controller/user.controller.js";
7 | import { Router } from "express";
8 | import { isUserAvailable } from "../middleware/auth.js";
9 |
10 | const router = Router();
11 |
12 | router.get("/", isUserAvailable, start);
13 | router.post("/register", registerUser);
14 | router.post("/login", loginUser);
15 | router.get("/logout", isUserAvailable, logoutUser);
16 |
17 | export default router;
18 |
--------------------------------------------------------------------------------
/Backend/src/db/index.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import { ApiError } from "../utils/ApiError.js";
3 |
4 | const connectDB = async () => {
5 | try {
6 | const conn = await mongoose.connect(process.env.MONGODB_URI, {
7 | dbName: "ai_resume_builder",
8 | });
9 |
10 | console.log(`MongoDB Connected: ${conn.connection.host}`);
11 | return conn;
12 | } catch (err) {
13 | throw new ApiError(500, "Database connection failed", [], err.stack);
14 | }
15 | };
16 |
17 | export { connectDB };
18 |
--------------------------------------------------------------------------------
/Backend/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | backend:
5 | build: .
6 | container_name: ai-resume-builder-backend
7 | ports:
8 | - "5001:5001"
9 | env_file:
10 | - .env
11 | depends_on:
12 | - mongodb
13 | command: ["node", "src/index.js"]
14 |
15 | mongodb:
16 | image: mongo:latest
17 | container_name: ai-resume-builder-db
18 | ports:
19 | - "27017:27017"
20 | volumes:
21 | - mongo_data:/data/db
22 |
23 | volumes:
24 | mongo_data:
25 |
--------------------------------------------------------------------------------
/Frontend/src/features/resume/resumeFeatures.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | resumeData:""
5 | };
6 | export const resumeSlice = createSlice({
7 | name: "editResume",
8 | initialState,
9 | reducers: {
10 | addResumeData: (state, action) => {
11 | state.resumeData = action.payload;
12 | },
13 | },
14 | });
15 |
16 | // Action creators are generated for each case reducer function
17 | export const { addResumeData } = resumeSlice.actions;
18 |
19 | export default resumeSlice.reducer;
20 |
--------------------------------------------------------------------------------
/Backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "type": "module",
6 | "scripts": {
7 | "start": "node src/index.js",
8 | "dev": "nodemon src/index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "description": "",
14 | "dependencies": {
15 | "bcrypt": "^5.1.1",
16 | "cookie-parser": "^1.4.6",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.4.5",
19 | "express": "^4.19.2",
20 | "jsonwebtoken": "^9.0.2",
21 | "mongoose": "^8.5.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Frontend/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Backend/src/models/experience.model.js:
--------------------------------------------------------------------------------
1 | // models/Experience.js
2 | import mongoose from 'mongoose';
3 |
4 | const { Schema } = mongoose;
5 |
6 | const experienceSchema = new Schema({
7 | title: {
8 | type: String
9 | },
10 | companyName: {
11 | type: String
12 | },
13 | city: {
14 | type: String
15 | },
16 | state: {
17 | type: String
18 | },
19 | startDate: {
20 | type: Date
21 | },
22 | endDate: {
23 | type: Date
24 | },
25 | currentlyWorking: {
26 | type: Boolean
27 | },
28 | workSummary: {
29 | type: String
30 | }
31 | }, { _id: false });
32 |
33 | export default experienceSchema;
34 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Backend/src/app.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import cookieParser from "cookie-parser";
3 | import userRouter from "./routes/user.routes.js";
4 | import resumeRouter from "./routes/resume.routes.js";
5 | import cors from "cors";
6 | import { config } from "dotenv";
7 | config();
8 |
9 | const app = express();
10 | app.use(express.json());
11 | app.use(express.urlencoded({ extended: true }));
12 | app.use(cookieParser());
13 |
14 |
15 | const corsOptions = {
16 | origin: [process.env.ALLOWED_SITE],
17 | credentials: true
18 | };
19 |
20 | app.use(cors(corsOptions));
21 |
22 | app.use("/api/users", userRouter);
23 | app.use("/api/resumes", resumeRouter);
24 |
25 | export default app;
26 |
--------------------------------------------------------------------------------
/Frontend/src/components/ui/textarea.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef(({ className, ...props }, ref) => {
6 | return (
7 | ()
14 | );
15 | })
16 | Textarea.displayName = "Textarea"
17 |
18 | export { Textarea }
19 |
--------------------------------------------------------------------------------
/Backend/src/routes/resume.routes.js:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import {
3 | start,
4 | createResume,
5 | getALLResume,
6 | getResume,
7 | updateResume,
8 | removeResume,
9 | } from "../controller/resume.controller.js";
10 | import { isUserAvailable } from "../middleware/auth.js";
11 |
12 | const router = Router();
13 |
14 | router.get("/", start);
15 | router.post("/createResume", isUserAvailable, createResume);
16 | router.get("/getAllResume", isUserAvailable, getALLResume);
17 | router.get("/getResume", isUserAvailable, getResume);
18 | router.put("/updateResume", isUserAvailable, updateResume);
19 | router.delete("/removeResume", isUserAvailable, removeResume);
20 |
21 | export default router;
22 |
--------------------------------------------------------------------------------
/Frontend/src/Services/AiModel.js:
--------------------------------------------------------------------------------
1 | import { Gem } from "lucide-react";
2 | import { GEMENI_API_KEY } from "../config/config";
3 | import { GoogleGenerativeAI } from "@google/generative-ai";
4 |
5 |
6 | const apiKey = GEMENI_API_KEY;
7 | const genAI = new GoogleGenerativeAI(apiKey);
8 |
9 | const model = genAI.getGenerativeModel({
10 | model: "gemini-1.5-flash",
11 | });
12 |
13 | const generationConfig = {
14 | temperature: 1,
15 | topP: 0.95,
16 | topK: 64,
17 | maxOutputTokens: 8192,
18 | responseMimeType: "application/json",
19 | };
20 |
21 | export const AIChatSession = model.startChat({
22 | generationConfig,
23 | // safetySettings: Adjust safety settings
24 | // See https://ai.google.dev/gemini-api/docs/safety-settings
25 | history: [],
26 | });
27 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Backend/src/utils/ApiError.js:
--------------------------------------------------------------------------------
1 | class ApiError extends Error {
2 | constructor(
3 | statusCode,
4 | message = "Something went wrong",
5 | errors = [],
6 | stack = ""
7 | ) {
8 | super(message);
9 | this.statusCode = statusCode;
10 | this.data = null;
11 | this.errors = errors;
12 | this.success = false;
13 | this.message = message;
14 |
15 | if (stack) {
16 | this.stack = stack;
17 | } else {
18 | Error.captureStackTrace(this, this.constructor);
19 | }
20 | }
21 |
22 | toJSON() {
23 | return {
24 | statusCode: this.statusCode,
25 | data: this.data,
26 | errors: this.errors,
27 | success: this.success,
28 | message: this.message,
29 | };
30 | }
31 | }
32 |
33 | export { ApiError };
34 |
--------------------------------------------------------------------------------
/Backend/src/models/education.model.js:
--------------------------------------------------------------------------------
1 | // models/Education.js
2 | import mongoose from "mongoose";
3 |
4 | const { Schema } = mongoose;
5 |
6 | const educationSchema = new Schema({
7 | universityName: {
8 | type: String,
9 | default: "",
10 | },
11 | degree: {
12 | type: String,
13 | default: "",
14 | },
15 | major: {
16 | type: String,
17 | default: "",
18 | },
19 | startDate: {
20 | type: String,
21 | default: null,
22 | },
23 | endDate: {
24 | type: String,
25 | default: null,
26 | },
27 | description: {
28 | type: String,
29 | default: "",
30 | },
31 | grade: {
32 | type: String,
33 | default: "",
34 | },
35 | gradeType: {
36 | type: String,
37 | default: "",
38 | },
39 | });
40 |
41 | export default educationSchema;
42 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/[resume_id]/EditResume.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import ResumeForm from "../components/ResumeForm";
3 | import PreviewPage from "../components/PreviewPage";
4 | import { useParams } from "react-router-dom";
5 | import { getResumeData } from "@/Services/resumeAPI";
6 | import { useDispatch } from "react-redux";
7 | import { addResumeData } from "@/features/resume/resumeFeatures";
8 |
9 | export function EditResume() {
10 | const { resume_id } = useParams();
11 | const dispatch = useDispatch();
12 | useEffect(() => {
13 | getResumeData(resume_id).then((data) => {
14 | dispatch(addResumeData(data.data));
15 | });
16 | }, [resume_id]);
17 | return (
18 |
22 | );
23 | }
24 |
25 | export default EditResume;
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Backend/src/middleware/auth.js:
--------------------------------------------------------------------------------
1 | import User from "../models/user.model.js";
2 | import jwt from "jsonwebtoken";
3 | import { ApiResponse } from "../utils/ApiResponse.js";
4 | import { ApiError } from "../utils/ApiError.js";
5 |
6 | const isUserAvailable = async (req, res, next) => {
7 | let { token } = req.cookies;
8 |
9 | if (!token) {
10 | return res.status(404).json(new ApiError(404, "User not authenticated."));
11 | }
12 |
13 | try {
14 | const decodedToken = await jwt.verify(token, process.env.JWT_SECRET_KEY);
15 | const user = await User.findById(decodedToken.id);
16 |
17 | if (!user) {
18 | return res.status(404).json(new ApiError(404, "User not found."));
19 | }
20 |
21 | req.user = user;
22 | next();
23 | } catch (err) {
24 | console.error("Error verifying token:", err);
25 | return res.status(500).json(new ApiError(500, "Internal Server Error.", [], err.stack));
26 | }
27 | };
28 |
29 | export { isUserAvailable };
30 |
--------------------------------------------------------------------------------
/Backend/src/models/user.model.js:
--------------------------------------------------------------------------------
1 | // models/User.js
2 | import mongoose from 'mongoose';
3 | import bcrypt from 'bcrypt';
4 |
5 | const { Schema, model } = mongoose;
6 |
7 | const userSchema = new Schema({
8 | fullName: {
9 | type: String,
10 | required: true
11 | },
12 | email: {
13 | type: String,
14 | required: true,
15 | unique: true
16 | },
17 | password: {
18 | type: String,
19 | required: true
20 | }
21 | }, { timestamps: true });
22 |
23 | // Pre-save middleware to hash password
24 | userSchema.pre('save', async function(next) {
25 | if (!this.isModified('password')) {
26 | next();
27 | }
28 | try {
29 | const salt = await bcrypt.genSalt(10);
30 | this.password = await bcrypt.hash(this.password, salt);
31 | next();
32 | } catch (err) {
33 | next(err);
34 | }
35 | });
36 |
37 | // Method to compare passwords
38 | userSchema.methods.comparePassword = function(candidatePassword) {
39 | return bcrypt.compare(candidatePassword, this.password);
40 | };
41 |
42 | const User = model('User', userSchema);
43 |
44 | export default User;
45 |
--------------------------------------------------------------------------------
/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/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/SkillsPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function SkillsPreview({ resumeInfo }) {
4 | return (
5 |
6 | {resumeInfo?.skills.length > 0 && (
7 |
8 |
14 | Skills
15 |
16 |
21 |
22 | )}
23 |
24 |
25 | {resumeInfo?.skills.map((skill, index) => (
26 |
27 |
{skill.name}
28 | {skill.name ? (
29 |
38 | ) : null}
39 |
40 | ))}
41 |
42 |
43 | );
44 | }
45 |
46 | export default SkillsPreview;
47 |
--------------------------------------------------------------------------------
/Frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet, useNavigate } from "react-router-dom";
2 | import Header from "./components/custom/Header";
3 | import { Toaster } from "./components/ui/sonner";
4 | import { useDispatch } from "react-redux";
5 | import { useSelector } from "react-redux";
6 | import { useEffect } from "react";
7 | import { addUserData } from "./features/user/userFeatures";
8 | import { startUser } from "./Services/login";
9 | import { resumeStore } from "./store/store";
10 | import { Provider } from "react-redux";
11 |
12 | function App() {
13 | const navigate = useNavigate();
14 | const user = useSelector((state) => state.editUser.userData);
15 | const dispatch = useDispatch();
16 |
17 | useEffect(() => {
18 | const fetchResponse = async () => {
19 | try {
20 | const response = await startUser();
21 | if (response.statusCode == 200) {
22 | dispatch(addUserData(response.data));
23 | } else {
24 | dispatch(addUserData(""));
25 | }
26 | } catch (error) {
27 | console.log("Got Error while fetching user from app", error.message);
28 | dispatch(addUserData(""));
29 | }
30 | };
31 | fetchResponse();
32 | }, []);
33 |
34 | if (!user) {
35 | navigate("/");
36 | }
37 |
38 | return (
39 | <>
40 |
41 |
42 |
43 |
44 |
45 | >
46 | );
47 | }
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/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 { RouterProvider, createBrowserRouter } from "react-router-dom";
6 | import HomePage from "./pages/home/HomePage.jsx";
7 | import Dashboard from "./pages/dashboard/Dashboard.jsx";
8 | import { EditResume } from "./pages/dashboard/edit-resume/[resume_id]/EditResume.jsx";
9 | import ViewResume from "./pages/dashboard/view-resume/[resume_id]/ViewResume.jsx";
10 | import AuthPage from "./pages/auth/customAuth/AuthPage.jsx";
11 | import { resumeStore } from "./store/store";
12 | import { Provider } from "react-redux";
13 |
14 | const router = createBrowserRouter([
15 | {
16 | element: ,
17 | children: [
18 | {
19 | path: "/dashboard",
20 | element: ,
21 | },
22 | {
23 | path: "/dashboard/edit-resume/:resume_id",
24 | element: ,
25 | },
26 | {
27 | path: "/dashboard/view-resume/:resume_id",
28 | element: ,
29 | },
30 | ],
31 | },
32 | {
33 | path: "/",
34 | element: ,
35 | },
36 | {
37 | path: "/auth/sign-in",
38 | element: ,
39 | },
40 | ]);
41 | ReactDOM.createRoot(document.getElementById("root")).render(
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/PreviewPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useSelector } from "react-redux";
3 | import PersonalDeatailPreview from "./preview-components/PersonalDeatailPreview";
4 | import SummeryPreview from "./preview-components/SummaryPreview";
5 | import ExperiencePreview from "./preview-components/ExperiencePreview";
6 | import EducationalPreview from "./preview-components/EducationalPreview";
7 | import SkillsPreview from "./preview-components/SkillsPreview";
8 | import ProjectPreview from "./preview-components/ProjectPreview";
9 |
10 | function PreviewPage() {
11 | const resumeData = useSelector((state) => state.editResume.resumeData);
12 | useEffect(() => {
13 | console.log("PreviewPage rendered ");
14 | }, [resumeData]);
15 | return (
16 |
22 |
23 | {}
24 |
25 | {resumeData?.experience &&
}
26 | {resumeData?.projects &&
}
27 | {resumeData?.education &&
}
28 | {resumeData?.skills &&
}
29 |
30 | );
31 | }
32 |
33 | export default PreviewPage;
34 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/PersonalDeatailPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function PersonalDeatailPreview({ resumeInfo }) {
4 | return (
5 |
6 |
12 | {resumeInfo?.firstName} {resumeInfo?.lastName}
13 |
14 |
15 | {resumeInfo?.jobTitle}
16 |
17 |
23 | {resumeInfo?.address}
24 |
25 |
26 |
27 |
33 | {resumeInfo?.phone}
34 |
35 |
41 | {resumeInfo?.email}
42 |
43 |
44 |
50 |
51 | );
52 | }
53 |
54 | export default PersonalDeatailPreview;
55 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/ProjectPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ProjectPreview({ resumeInfo }) {
4 | return (
5 |
6 | {resumeInfo?.projects.length > 0 && (
7 |
8 |
14 | Personal Project
15 |
16 |
21 |
22 | )}
23 |
24 | {resumeInfo?.projects?.map((project, index) => (
25 |
26 |
32 | {project?.projectName}
33 |
34 |
35 | {project?.techStack?.length > 0 && (
36 | Tech Stack: {project?.techStack?.split(",").join(" | ")}
37 | )}
38 |
39 |
43 |
44 | ))}
45 |
46 | );
47 | }
48 |
49 | export default ProjectPreview;
50 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useSelector } from "react-redux";
3 | import { getAllResumeData } from "@/Services/resumeAPI";
4 | import AddResume from "./components/AddResume";
5 | import ResumeCard from "./components/ResumeCard";
6 |
7 | function Dashboard() {
8 | const user = useSelector((state) => state.editUser.userData);
9 | const [resumeList, setResumeList] = React.useState([]);
10 |
11 | const fetchAllResumeData = async () => {
12 | try {
13 | const resumes = await getAllResumeData();
14 | console.log(
15 | `Printing from DashBoard List of Resumes got from Backend`,
16 | resumes.data
17 | );
18 | setResumeList(resumes.data);
19 | } catch (error) {
20 | console.log("Error from dashboard", error.message);
21 | }
22 | };
23 |
24 | useEffect(() => {
25 | fetchAllResumeData();
26 | }, [user]);
27 |
28 | return (
29 |
30 |
My Resume
31 |
Start creating your Ai resume for next Job role
32 |
33 |
34 | {resumeList.length > 0 &&
35 | resumeList.map((resume, index) => (
36 |
41 | ))}
42 |
43 |
44 | );
45 | }
46 |
47 | export default Dashboard;
48 |
--------------------------------------------------------------------------------
/Backend/src/models/resume.model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import educationSchema from "./education.model.js";
3 | const resumeSchema = new mongoose.Schema({
4 | firstName: { type: String, default: "" },
5 | lastName: { type: String, default: "" },
6 | email: { type: String, default: "" },
7 | title: { type: String, required: true },
8 | summary: { type: String, default: "" },
9 | jobTitle: { type: String, default: "" },
10 | phone: { type: String, default: "" },
11 | address: { type: String, default: "" },
12 | user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
13 | experience: [
14 | {
15 | title: { type: String },
16 | companyName: { type: String },
17 | city: { type: String },
18 | state: { type: String },
19 | startDate: { type: String },
20 | endDate: { type: String },
21 | currentlyWorking: { type: String },
22 | workSummary: { type: String },
23 | },
24 | ],
25 | education: [
26 | {
27 | type: educationSchema,
28 | },
29 | ],
30 | skills: [
31 | {
32 | name: { type: String },
33 | rating: { type: Number },
34 | },
35 | ],
36 | projects: [
37 | {
38 | projectName: { type: String },
39 | techStack: { type: String },
40 | projectSummary: { type: String },
41 | },
42 | ],
43 | themeColor: { type: String, required: true },
44 | createdAt: { type: Date, default: Date.now },
45 | updatedAt: { type: Date, default: Date.now },
46 | });
47 |
48 | const Resume = mongoose.model("Resume", resumeSchema);
49 |
50 | export default Resume;
51 |
--------------------------------------------------------------------------------
/Frontend/src/Services/login.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { VITE_APP_URL } from "@/config/config";
3 |
4 | const axiosInstance = axios.create({
5 | baseURL: VITE_APP_URL + "api/",
6 | headers: {
7 | "Content-Type": "application/json",
8 | },
9 | withCredentials: true,
10 | });
11 |
12 | const startUser = async () => {
13 | try {
14 | const response = await axiosInstance.get("users/");
15 | return response.data;
16 | } catch (error) {
17 | throw new Error(
18 | error?.response?.data?.message || error?.message || "Something Went Wrong"
19 | );
20 | }
21 | };
22 |
23 | const registerUser = async (data) => {
24 | try {
25 | const response = await axiosInstance.post("users/register/", data);
26 | return response.data;
27 | } catch (error) {
28 | throw new Error(
29 | error?.response?.data?.message || error?.message || "Something Went Wrong"
30 | );
31 | }
32 | };
33 |
34 | const loginUser = async (data) => {
35 | try {
36 | const response = await axiosInstance.post("users/login/", data);
37 | return response.data;
38 | } catch (error) {
39 | throw new Error(
40 | error?.response?.data?.message || error?.message || "Something Went Wrong"
41 | );
42 | }
43 | };
44 |
45 | const logoutUser = async () => {
46 | try {
47 | const response = await axiosInstance.get("users/logout/");
48 | return response.data;
49 | } catch (error) {
50 | throw new Error(
51 | error?.response?.data?.message || error?.message || "Something Went Wrong"
52 | );
53 | }
54 | };
55 |
56 | export { startUser, registerUser, loginUser, logoutUser };
57 |
--------------------------------------------------------------------------------
/Frontend/src/components/custom/Header.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import logo from "/logo.svg";
3 | import { Button } from "../ui/button";
4 | import { Link, useNavigate } from "react-router-dom";
5 | import { useDispatch } from "react-redux";
6 | import { logoutUser } from "@/Services/login";
7 | import { addUserData } from "@/features/user/userFeatures";
8 |
9 | function Header({user}) {
10 | const dispatch = useDispatch();
11 | const navigate = useNavigate();
12 |
13 | useEffect(() => {
14 | if(user){
15 | console.log("Printing From Header User Found");
16 | }
17 | else{
18 | console.log("Printing From Header User Not Found");
19 | }
20 | }, []);
21 |
22 | const handleLogout = async () => {
23 | try {
24 | const response = await logoutUser();
25 | if (response.statusCode == 200) {
26 | dispatch(addUserData(""));
27 | navigate("/");
28 | }
29 | } catch (error) {
30 | console.log(error.message);
31 | }
32 | };
33 |
34 | return (
35 |
58 | );
59 | }
60 |
61 | export default Header;
62 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/EducationalPreview.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function EducationalPreview({ resumeInfo }) {
4 | return (
5 |
6 | {resumeInfo?.education.length > 0 && (
7 |
8 |
14 | Education
15 |
16 |
21 |
22 | )}
23 |
24 | {resumeInfo?.education.map((education, index) => (
25 |
26 |
32 | {education.universityName}
33 |
34 |
35 | {education?.degree}
36 | {education?.degree && education?.major ? " in " : null}
37 | {education?.major}
38 |
39 | {education?.startDate}{" "}
40 | {education?.startDate && education?.endDate ? " - " : null}{" "}
41 | {education?.endDate}
42 |
43 |
44 |
45 | {education?.grade ? `${education?.gradeType} - ${education?.grade}` : null}
46 |
47 |
{education?.description}
48 |
49 | ))}
50 |
51 | );
52 | }
53 |
54 | export default EducationalPreview;
55 |
--------------------------------------------------------------------------------
/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/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-resume-builder-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 | "@clerk/clerk-react": "^5.2.5",
14 | "@google/generative-ai": "^0.14.0",
15 | "@radix-ui/react-alert-dialog": "^1.1.1",
16 | "@radix-ui/react-dialog": "^1.1.1",
17 | "@radix-ui/react-popover": "^1.1.1",
18 | "@radix-ui/react-slot": "^1.1.0",
19 | "@reduxjs/toolkit": "^2.2.5",
20 | "@smastrom/react-rating": "^1.5.0",
21 | "axios": "^1.7.2",
22 | "class-variance-authority": "^0.7.0",
23 | "clsx": "^2.1.1",
24 | "framer-motion": "^11.3.6",
25 | "lucide-react": "^0.397.0",
26 | "next-themes": "^0.3.0",
27 | "react": "^18.3.1",
28 | "react-dom": "^18.3.1",
29 | "react-draft-wysiwyg": "^1.15.0",
30 | "react-icons": "^5.2.1",
31 | "react-redux": "^9.1.2",
32 | "react-router-dom": "^6.24.0",
33 | "react-simple-wysiwyg": "^3.0.3",
34 | "react-web-share": "^2.0.2",
35 | "sonner": "^1.5.0",
36 | "tailwind-merge": "^2.3.0",
37 | "tailwindcss-animate": "^1.0.7",
38 | "uuid": "^10.0.0"
39 | },
40 | "devDependencies": {
41 | "@types/node": "^20.14.9",
42 | "@types/react": "^18.3.3",
43 | "@types/react-dom": "^18.3.0",
44 | "@vitejs/plugin-react": "^4.3.1",
45 | "autoprefixer": "^10.4.19",
46 | "eslint": "^8.57.0",
47 | "eslint-plugin-react": "^7.34.2",
48 | "eslint-plugin-react-hooks": "^4.6.2",
49 | "eslint-plugin-react-refresh": "^0.4.7",
50 | "postcss": "^8.4.38",
51 | "tailwindcss": "^3.4.4",
52 | "vite": "^5.3.1"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Frontend/src/Services/GlobalApi.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { API_KEY } from "@/config/config";
3 |
4 | const axiosInstance = axios.create({
5 | baseURL: import.meta.env.VITE_BASE_URL + "api/",
6 | headers: {
7 | "Content-Type": "application/json",
8 | Authorization: `Bearer ${API_KEY}`,
9 | },
10 | });
11 |
12 | const createNewResume = async (data) => {
13 | try {
14 | const response = await axiosInstance.post(
15 | "/resume-builder-collections",
16 | data
17 | );
18 | return response.data;
19 | } catch (error) {
20 | console.error("Error creating new resume:", error);
21 | }
22 | };
23 |
24 | const getResumes = async (user_email) => {
25 | try {
26 | const response = await axiosInstance.get(
27 | "/resume-builder-collections?filters[user_email][$eq]=" + user_email
28 | );
29 | return response.data;
30 | } catch (error) {
31 | console.error("Error getting resumes:", error);
32 | }
33 | };
34 |
35 | const updateResumeData = async (id, data) => {
36 | try {
37 | const response = await axiosInstance.put(
38 | `/resume-builder-collections/${id}`,
39 | data
40 | );
41 | return response.data;
42 | } catch (error) {
43 | console.error("Error updating resume data:", error);
44 | }
45 | };
46 |
47 | const getResumeInfo = async (id) => {
48 | try {
49 | const response = await axiosInstance.get(
50 | `/resume-builder-collections/${id}?populate=*`
51 | );
52 | return response.data;
53 | } catch (error) {
54 | console.error("Error getting resume data:", error);
55 | }
56 | };
57 |
58 | const deleteResume = async (id) => {
59 | try {
60 | const response = await axiosInstance.delete(
61 | `/resume-builder-collections/${id}`
62 | );
63 | return response.data;
64 | } catch (error) {
65 | console.error("Error deleting resume:", error);
66 | }
67 | };
68 |
69 | export {
70 | createNewResume,
71 | getResumes,
72 | updateResumeData,
73 | getResumeInfo,
74 | deleteResume,
75 | };
76 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/preview-components/ExperiencePreview.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ExperiencePreview({ resumeInfo }) {
4 | return (
5 |
6 | {resumeInfo?.experience.length > 0 && (
7 |
8 |
14 | Professional Experience
15 |
16 |
21 |
22 | )}
23 |
24 | {resumeInfo?.experience?.map((experience, index) => (
25 |
26 |
32 | {experience?.title}
33 |
34 |
35 | {experience?.companyName}
36 | {experience?.companyName && experience?.city ? ", " : null}
37 | {experience?.city}
38 | {experience?.city && experience?.state ? ", " : null}
39 | {experience?.state}
40 |
41 | {experience?.startDate}{" "}
42 | {experience?.startDate && experience?.currentlyWorking
43 | ? "Present"
44 | : experience.endDate
45 | ? "To"
46 | : null}{" "}
47 | {experience?.currentlyWorking ? "Present" : experience.endDate}{" "}
48 |
49 |
50 | {/*
51 | {experience.workSummery}
52 |
*/}
53 |
57 |
58 | ))}
59 |
60 | );
61 | }
62 |
63 | export default ExperiencePreview;
64 |
--------------------------------------------------------------------------------
/Frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @media print {
6 | #noPrint {
7 | display: none;
8 | }
9 | #print {
10 | display: block;
11 | }
12 | #printHeader {
13 | display: none;
14 | }
15 | body {
16 | margin: 0;
17 | padding: 0;
18 | box-sizing: border-box;
19 | }
20 | }
21 |
22 | @page {
23 | size: A4;
24 | margin: 0;
25 | }
26 |
27 | @layer base {
28 | :root {
29 | --background: 0 0% 100%;
30 | --foreground: 0 0% 3.9%;
31 |
32 | --card: 0 0% 100%;
33 | --card-foreground: 0 0% 3.9%;
34 |
35 | --popover: 0 0% 100%;
36 | --popover-foreground: 0 0% 3.9%;
37 |
38 | --primary: 0 0% 9%;
39 | --primary-foreground: 0 0% 98%;
40 |
41 | --secondary: 0 0% 96.1%;
42 | --secondary-foreground: 0 0% 9%;
43 |
44 | --muted: 0 0% 96.1%;
45 | --muted-foreground: 0 0% 45.1%;
46 |
47 | --accent: 0 0% 96.1%;
48 | --accent-foreground: 0 0% 9%;
49 |
50 | --destructive: 0 84.2% 60.2%;
51 | --destructive-foreground: 0 0% 98%;
52 |
53 | --border: 0 0% 89.8%;
54 | --input: 0 0% 89.8%;
55 | --ring: 0 0% 3.9%;
56 |
57 | --radius: 0.5rem;
58 | }
59 |
60 | .dark {
61 | --background: 0 0% 3.9%;
62 | --foreground: 0 0% 98%;
63 |
64 | --card: 0 0% 3.9%;
65 | --card-foreground: 0 0% 98%;
66 |
67 | --popover: 0 0% 3.9%;
68 | --popover-foreground: 0 0% 98%;
69 |
70 | --primary: 0 0% 98%;
71 | --primary-foreground: 0 0% 9%;
72 |
73 | --secondary: 0 0% 14.9%;
74 | --secondary-foreground: 0 0% 98%;
75 |
76 | --muted: 0 0% 14.9%;
77 | --muted-foreground: 0 0% 63.9%;
78 |
79 | --accent: 0 0% 14.9%;
80 | --accent-foreground: 0 0% 98%;
81 |
82 | --destructive: 0 62.8% 30.6%;
83 | --destructive-foreground: 0 0% 98%;
84 |
85 | --border: 0 0% 14.9%;
86 | --input: 0 0% 14.9%;
87 | --ring: 0 0% 83.1%;
88 | }
89 | }
90 |
91 | @layer base {
92 | * {
93 | @apply border-border;
94 | }
95 | body {
96 | @apply bg-background text-foreground;
97 | }
98 | }
99 | .rsw-ce ul {
100 | list-style: disc;
101 | padding-left: 2em;
102 | }
103 |
104 | .rsw-ce ol {
105 | list-style: decimal;
106 | padding-left: 2em;
107 | }
108 |
--------------------------------------------------------------------------------
/Frontend/src/Services/resumeAPI.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { VITE_APP_URL } from "@/config/config";
3 |
4 | const axiosInstance = axios.create({
5 | baseURL: VITE_APP_URL + "api/",
6 | headers: {
7 | "Content-Type": "application/json",
8 | },
9 | withCredentials: true,
10 | });
11 |
12 | const createNewResume = async (data) => {
13 | try {
14 | const response = await axiosInstance.post(
15 | "resumes/createResume",
16 | data.data
17 | );
18 | return response.data;
19 | } catch (error) {
20 | // console.log("Eroor in getting all the resumes ",error);
21 | throw new Error(
22 | error?.response?.data?.message || error?.message || "Something Went Wrong"
23 | );
24 | }
25 | };
26 |
27 | const getAllResumeData = async () => {
28 | try {
29 | const response = await axiosInstance.get("resumes/getAllResume");
30 | return response.data;
31 | } catch (error) {
32 | // console.log("Eroor in getting all the resumes ",error);
33 | throw new Error(
34 | error?.response?.data?.message || error?.message || "Something Went Wrong"
35 | );
36 | }
37 | };
38 |
39 | const getResumeData = async (resumeID) => {
40 | try {
41 | const response = await axiosInstance.get(
42 | `resumes/getResume?id=${resumeID}`
43 | );
44 | return response.data;
45 | } catch (error) {
46 | throw new Error(
47 | error?.response?.data?.message || error?.message || "Something Went Wrong"
48 | );
49 | }
50 | };
51 |
52 | const updateThisResume = async (resumeID, data) => {
53 | try {
54 | const response = await axiosInstance.put(
55 | `resumes/updateResume?id=${resumeID}`,
56 | data.data
57 | );
58 | return response.data;
59 | } catch (error) {
60 | throw new Error(
61 | error?.response?.data?.message || error?.message || "Something Went Wrong"
62 | );
63 | }
64 | };
65 |
66 | const deleteThisResume = async (resumeID) => {
67 | try {
68 | const response = await axiosInstance.delete(
69 | `resumes/removeResume?id=${resumeID}`
70 | );
71 | return response.data;
72 | } catch (error) {
73 | throw new Error(
74 | error?.response?.data?.message || error?.message || "Something Went Wrong"
75 | );
76 | }
77 | };
78 |
79 | export {
80 | getAllResumeData,
81 | deleteThisResume,
82 | getResumeData,
83 | updateThisResume,
84 | createNewResume,
85 | };
86 |
--------------------------------------------------------------------------------
/Frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{js,jsx}',
6 | './components/**/*.{js,jsx}',
7 | './app/**/*.{js,jsx}',
8 | './src/**/*.{js,jsx}',
9 | "./index.html",
10 | "./src/**/*.{js,ts,jsx,tsx}",
11 | ],
12 | prefix: "",
13 | theme: {
14 | container: {
15 | center: true,
16 | padding: "2rem",
17 | screens: {
18 | "2xl": "1400px",
19 | },
20 | },
21 | extend: {
22 | colors: {
23 | border: "hsl(var(--border))",
24 | input: "hsl(var(--input))",
25 | ring: "hsl(var(--ring))",
26 | background: "hsl(var(--background))",
27 | foreground: "hsl(var(--foreground))",
28 | primary: {
29 | DEFAULT: "#4ade80",
30 | foreground: "hsl(var(--primary-foreground))",
31 | },
32 | secondary: {
33 | DEFAULT: "hsl(var(--secondary))",
34 | foreground: "hsl(var(--secondary-foreground))",
35 | },
36 | destructive: {
37 | DEFAULT: "hsl(var(--destructive))",
38 | foreground: "hsl(var(--destructive-foreground))",
39 | },
40 | muted: {
41 | DEFAULT: "hsl(var(--muted))",
42 | foreground: "hsl(var(--muted-foreground))",
43 | },
44 | accent: {
45 | DEFAULT: "hsl(var(--accent))",
46 | foreground: "hsl(var(--accent-foreground))",
47 | },
48 | popover: {
49 | DEFAULT: "hsl(var(--popover))",
50 | foreground: "hsl(var(--popover-foreground))",
51 | },
52 | card: {
53 | DEFAULT: "hsl(var(--card))",
54 | foreground: "hsl(var(--card-foreground))",
55 | },
56 | },
57 | borderRadius: {
58 | lg: "var(--radius)",
59 | md: "calc(var(--radius) - 2px)",
60 | sm: "calc(var(--radius) - 4px)",
61 | },
62 | keyframes: {
63 | "accordion-down": {
64 | from: { height: "0" },
65 | to: { height: "var(--radix-accordion-content-height)" },
66 | },
67 | "accordion-up": {
68 | from: { height: "var(--radix-accordion-content-height)" },
69 | to: { height: "0" },
70 | },
71 | },
72 | animation: {
73 | "accordion-down": "accordion-down 0.2s ease-out",
74 | "accordion-up": "accordion-up 0.2s ease-out",
75 | },
76 | },
77 | },
78 | plugins: ["tailwindcss-animate"],
79 | }
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/view-resume/[resume_id]/ViewResume.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { Button } from "@/components/ui/button";
4 | import { getResumeData } from "@/Services/resumeAPI";
5 | import ResumePreview from "../../edit-resume/components/PreviewPage";
6 | import { useDispatch } from "react-redux";
7 | import { addResumeData } from "@/features/resume/resumeFeatures";
8 | import { RWebShare } from "react-web-share";
9 | import { toast } from "sonner";
10 |
11 | function ViewResume() {
12 | const [resumeInfo, setResumeInfo] = React.useState({});
13 | const { resume_id } = useParams();
14 | const dispatch = useDispatch();
15 |
16 | useEffect(() => {
17 | fetchResumeInfo();
18 | }, []);
19 | const fetchResumeInfo = async () => {
20 | const response = await getResumeData(resume_id);
21 | // console.log(response.data);
22 | dispatch(addResumeData(response.data));
23 | };
24 |
25 | const HandleDownload = () => {
26 | window.print();
27 | };
28 | return (
29 | <>
30 |
31 |
32 |
33 |
34 | Congrats! Your Ultimate AI generates Resume is ready !{" "}
35 |
36 |
37 | Now you are ready to download your resume and you can share unique
38 | resume url with your friends and family{" "}
39 |
40 |
41 |
42 | toast("Resume Shared Successfully")}
49 | >
50 |
51 |
52 |
53 |
54 |
55 |
63 |
64 | >
65 | );
66 | }
67 |
68 | export default ViewResume;
69 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/ThemeColor.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import {
3 | Popover,
4 | PopoverContent,
5 | PopoverTrigger,
6 | } from "@/components/ui/popover";
7 | import { Button } from "@/components/ui/button";
8 | import { Palette } from "lucide-react";
9 | // import { ResumeInfoContext } from '@/context/ResumeInfoContext'
10 | // import GlobalApi from './../../../../service/GlobalApi'
11 | import { useParams } from "react-router-dom";
12 | import { toast } from "sonner";
13 | import { useDispatch } from "react-redux";
14 | import { addResumeData } from "@/features/resume/resumeFeatures";
15 | import { updateResumeData } from "@/Services/GlobalApi";
16 | import { updateThisResume } from "@/Services/resumeAPI";
17 |
18 | function ThemeColor({ resumeInfo }) {
19 | const dispatch = useDispatch();
20 | const colors = [
21 | "#FF5733",
22 | "#33FF57",
23 | "#3357FF",
24 | "#FF33A1",
25 | "#A133FF",
26 | "#33FFA1",
27 | "#FF7133",
28 | "#71FF33",
29 | "#7133FF",
30 | "#FF3371",
31 | "#33FF71",
32 | "#3371FF",
33 | "#A1FF33",
34 | "#33A1FF",
35 | "#FF5733",
36 | "#5733FF",
37 | "#33FF5A",
38 | "#5A33FF",
39 | "#FF335A",
40 | "#335AFF",
41 | ];
42 |
43 | const [selectedColor, setSelectedColor] = useState();
44 | const { resume_id } = useParams();
45 | const onColorSelect = async (color) => {
46 | setSelectedColor(color);
47 | dispatch(
48 | addResumeData({
49 | ...resumeInfo,
50 | themeColor: color,
51 | })
52 | );
53 | const data = {
54 | data: {
55 | themeColor: color,
56 | },
57 | };
58 | await updateThisResume(resume_id, data)
59 | .then(() => {
60 | toast.success("Theme Color Updated");
61 | })
62 | .catch((error) => {
63 | toast.error("Error updating theme color");
64 | });
65 | // console.log(" COlor Data to be updated", data);
66 | };
67 |
68 | return (
69 |
70 |
71 |
75 |
76 |
77 | Select Theme Color
78 |
79 | {colors.map((item, index) => (
80 |
onColorSelect(item)}
83 | className={`h-5 w-5 rounded-full cursor-pointer
84 | hover:border-black border
85 | ${selectedColor == item && "border border-black"}
86 | `}
87 | style={{
88 | background: item,
89 | }}
90 | >
91 | ))}
92 |
93 |
94 |
95 | );
96 | }
97 |
98 | export default ThemeColor;
99 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/components/AddResume.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useState } from "react";
3 | import { CopyPlus, Loader } from "lucide-react";
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogDescription,
8 | DialogHeader,
9 | DialogTitle,
10 | } from "@/components/ui/dialog";
11 | import { Button } from "@/components/ui/button";
12 | import { Input } from "@/components/ui/input";
13 | import { createNewResume } from "@/Services/resumeAPI";
14 | import { useNavigate } from "react-router-dom";
15 |
16 | function AddResume() {
17 | const [isDialogOpen, setOpenDialog] = useState(false);
18 | const [resumetitle, setResumetitle] = useState("");
19 | const [loading, setLoading] = useState(false);
20 | const Navigate = useNavigate();
21 |
22 | const createResume = async () => {
23 | setLoading(true);
24 | if (resumetitle === "")
25 | return console.log("Please add a title to your resume");
26 | const data = {
27 | data: {
28 | title: resumetitle,
29 | themeColor: "#000000",
30 | },
31 | };
32 | console.log(`Creating Resume ${resumetitle}`);
33 | createNewResume(data)
34 | .then((res) => {
35 | console.log("Prinitng From AddResume Respnse of Create Resume", res);
36 | Navigate(`/dashboard/edit-resume/${res.data.resume._id}`);
37 | })
38 | .finally(() => {
39 | setLoading(false);
40 | setResumetitle("");
41 | });
42 | };
43 | return (
44 | <>
45 | setOpenDialog(true)}
48 | >
49 |
50 |
51 |
80 | >
81 | );
82 | }
83 |
84 | export default AddResume;
85 |
--------------------------------------------------------------------------------
/Frontend/src/components/custom/SimpeRichTextEditor.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | BtnBold,
4 | BtnBulletList,
5 | BtnItalic,
6 | BtnLink,
7 | BtnNumberedList,
8 | BtnStrikeThrough,
9 | BtnUnderline,
10 | Editor,
11 | EditorProvider,
12 | Separator,
13 | Toolbar,
14 | } from "react-simple-wysiwyg";
15 | import { AIChatSession } from "@/Services/AiModel";
16 | import { Button } from "../ui/button";
17 | import { toast } from "sonner";
18 | import { Sparkles, LoaderCircle } from "lucide-react";
19 |
20 | const PROMPT = `Create a JSON object with the following fields:
21 | "projectName": A string representing the project
22 | "techStack":A string representing the project tech stack
23 | "projectSummary": An array of strings, each representing a bullet point in html format describing relevant experience for the given project tittle and tech stack
24 | projectName-"{projectName}"
25 | techStack-"{techStack}"`;
26 | function SimpeRichTextEditor({ index, onRichTextEditorChange, resumeInfo }) {
27 | const [value, setValue] = useState(
28 | resumeInfo?.projects[index]?.projectSummary || ""
29 | );
30 | const [loading, setLoading] = useState(false);
31 |
32 | useEffect(() => {
33 | onRichTextEditorChange(value);
34 | }, [value]);
35 |
36 | const GenerateSummaryFromAI = async () => {
37 | if (
38 | !resumeInfo?.projects[index]?.projectName ||
39 | !resumeInfo?.projects[index]?.techStack
40 | ) {
41 | toast("Add Project Name and Tech Stack to generate summary");
42 | return;
43 | }
44 | setLoading(true);
45 |
46 | const prompt = PROMPT.replace(
47 | "{projectName}",
48 | resumeInfo?.projects[index]?.projectName
49 | ).replace("{techStack}", resumeInfo?.projects[index]?.techStack);
50 | console.log("Prompt", prompt);
51 | const result = await AIChatSession.sendMessage(prompt);
52 | const resp = JSON.parse(result.response.text());
53 | console.log("Response", resp);
54 | await setValue(resp.projectSummary?.join(""));
55 | setLoading(false);
56 | };
57 |
58 | return (
59 |
60 |
61 |
62 |
77 |
78 |
79 | {
82 | setValue(e.target.value);
83 | onRichTextEditorChange(value);
84 | }}
85 | >
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | );
101 | }
102 |
103 | export default SimpeRichTextEditor;
104 |
--------------------------------------------------------------------------------
/Frontend/src/components/custom/RichTextEditor.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | BtnBold,
4 | BtnBulletList,
5 | BtnItalic,
6 | BtnLink,
7 | BtnNumberedList,
8 | BtnStrikeThrough,
9 | BtnUnderline,
10 | Editor,
11 | EditorProvider,
12 | Separator,
13 | Toolbar,
14 | } from "react-simple-wysiwyg";
15 | import { AIChatSession } from "@/Services/AiModel";
16 | import { Button } from "../ui/button";
17 | import { toast } from "sonner";
18 | import { Sparkles, LoaderCircle } from "lucide-react";
19 |
20 | const PROMPT = `Create a JSON object with the following fields:
21 | "position_Title": A string representing the job title.
22 | "experience": An array of strings, each representing a bullet point describing relevant experience for the given job title in html format.
23 | For the Job Title "{positionTitle}", create a JSON object with the following fields:
24 | The experience array should contain 5-7 bullet points. Each bullet point should be a concise description of a relevant skill, responsibility, or achievement.`;
25 | function RichTextEditor({ onRichTextEditorChange, index, resumeInfo }) {
26 | const [value, setValue] = useState(
27 | resumeInfo?.experience[index]?.workSummary || ""
28 | );
29 | const [loading, setLoading] = useState(false);
30 |
31 | useEffect(() => {
32 | onRichTextEditorChange(value);
33 | }, [value]);
34 |
35 | const GenerateSummaryFromAI = async () => {
36 | if (!resumeInfo?.experience[index]?.title) {
37 | toast("Please Add Position Title");
38 | return;
39 | }
40 | setLoading(true);
41 |
42 | const prompt = PROMPT.replace(
43 | "{positionTitle}",
44 | resumeInfo.experience[index].title
45 | );
46 | const result = await AIChatSession.sendMessage(prompt);
47 | console.log(typeof result.response.text());
48 | console.log(JSON.parse(result.response.text()));
49 | const resp = JSON.parse(result.response.text());
50 | await setValue(
51 | resp.experience
52 | ? resp.experience?.join("")
53 | : resp.experience_bullets?.join("")
54 | );
55 | setLoading(false);
56 | };
57 |
58 | return (
59 |
60 |
61 |
62 |
77 |
78 |
79 | {
82 | setValue(e.target.value);
83 | onRichTextEditorChange(value);
84 | }}
85 | >
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | );
101 | }
102 |
103 | export default RichTextEditor;
104 |
--------------------------------------------------------------------------------
/Frontend/src/components/ui/alert-dialog.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { buttonVariants } from "@/components/ui/button"
6 |
7 | const AlertDialog = AlertDialogPrimitive.Root
8 |
9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10 |
11 | const AlertDialogPortal = AlertDialogPrimitive.Portal
12 |
13 | const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
14 |
21 | ))
22 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
23 |
24 | const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (
25 |
26 |
27 |
34 |
35 | ))
36 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
37 |
38 | const AlertDialogHeader = ({
39 | className,
40 | ...props
41 | }) => (
42 |
45 | )
46 | AlertDialogHeader.displayName = "AlertDialogHeader"
47 |
48 | const AlertDialogFooter = ({
49 | className,
50 | ...props
51 | }) => (
52 |
55 | )
56 | AlertDialogFooter.displayName = "AlertDialogFooter"
57 |
58 | const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (
59 |
60 | ))
61 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
62 |
63 | const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (
64 |
68 | ))
69 | AlertDialogDescription.displayName =
70 | AlertDialogPrimitive.Description.displayName
71 |
72 | const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (
73 |
74 | ))
75 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
76 |
77 | const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (
78 |
82 | ))
83 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
84 |
85 | export {
86 | AlertDialog,
87 | AlertDialogPortal,
88 | AlertDialogOverlay,
89 | AlertDialogTrigger,
90 | AlertDialogContent,
91 | AlertDialogHeader,
92 | AlertDialogFooter,
93 | AlertDialogTitle,
94 | AlertDialogDescription,
95 | AlertDialogAction,
96 | AlertDialogCancel,
97 | }
98 |
--------------------------------------------------------------------------------
/Frontend/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
24 | ));
25 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
26 | const DialogContent = React.forwardRef(
27 | ({ setOpenDialog, className, children, ...props }, ref) => (
28 |
29 |
30 |
38 | {children}
39 |
40 | {
43 | setOpenDialog(false);
44 | }}
45 | />
46 | Close
47 |
48 |
49 |
50 | )
51 | );
52 | DialogContent.displayName = DialogPrimitive.Content.displayName;
53 |
54 | const DialogHeader = ({ className, ...props }) => (
55 |
62 | );
63 | DialogHeader.displayName = "DialogHeader";
64 |
65 | const DialogFooter = ({ className, ...props }) => (
66 |
73 | );
74 | DialogFooter.displayName = "DialogFooter";
75 |
76 | const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
77 |
85 | ));
86 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
87 |
88 | const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
89 |
94 | ));
95 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
96 |
97 | export {
98 | Dialog,
99 | DialogPortal,
100 | DialogOverlay,
101 | DialogClose,
102 | DialogTrigger,
103 | DialogContent,
104 | DialogHeader,
105 | DialogFooter,
106 | DialogTitle,
107 | DialogDescription,
108 | };
109 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/ResumeForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { Button } from "@/components/ui/button";
4 | import PersonalDetails from "./form-components/PersonalDetails";
5 | import Summary from "./form-components/Summary";
6 | import Experience from "./form-components/Experience";
7 | import Education from "./form-components/Education";
8 | import Skills from "./form-components/Skills";
9 | import Project from "./form-components/Project";
10 | import { ArrowLeft, ArrowRight, HomeIcon } from "lucide-react";
11 | import { Link } from "react-router-dom";
12 | import ThemeColor from "./ThemeColor";
13 |
14 | function ResumeForm() {
15 | const [currentIndex, setCurrentIndex] = useState(0);
16 | const [enanbledNext, setEnabledNext] = useState(true);
17 | const [enanbledPrev, setEnabledPrev] = useState(true);
18 | const resumeInfo = useSelector((state) => state.editResume.resumeData);
19 |
20 | useEffect(() => {
21 | if (currentIndex === 0) {
22 | setEnabledPrev(false);
23 | } else if (currentIndex == 1) {
24 | setEnabledPrev(true);
25 | } else if (currentIndex === 4) {
26 | setEnabledNext(true);
27 | } else if (currentIndex === 5) {
28 | setEnabledNext(false);
29 | }
30 | }, [currentIndex]);
31 |
32 | // To Add Dummy Data
33 | // useEffect(() => {
34 | // dispatch(addResumeData(data));
35 | // }, []);
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 | {currentIndex > 0 && (
50 |
61 | )}
62 | {currentIndex < 5 && (
63 |
74 | )}
75 |
76 |
77 | {currentIndex === 0 && (
78 |
82 | )}
83 | {currentIndex === 1 && (
84 |
89 | )}
90 | {currentIndex === 2 && (
91 |
96 | )}
97 | {currentIndex === 3 && (
98 |
103 | )}
104 | {currentIndex === 4 && (
105 |
110 | )}
111 | {currentIndex === 5 && (
112 |
117 | )}
118 |
119 | );
120 | }
121 |
122 | export default ResumeForm;
123 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/Skills.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Button } from "@/components/ui/button";
3 | import { Input } from "@/components/ui/input";
4 | import { Rating } from "@smastrom/react-rating";
5 | import "@smastrom/react-rating/style.css";
6 | import { LoaderCircle } from "lucide-react";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import { addResumeData } from "@/features/resume/resumeFeatures";
9 | import { updateResumeData } from "@/Services/GlobalApi";
10 | import { useParams } from "react-router-dom";
11 | import { toast } from "sonner";
12 | import { updateThisResume } from "@/Services/resumeAPI";
13 |
14 | function Skills({ resumeInfo, enanbledNext }) {
15 | const [loading, setLoading] = React.useState(false);
16 | const [skillsList, setSkillsList] = React.useState(
17 | resumeInfo?.skills || [
18 | {
19 | name: "",
20 | rating: 0,
21 | },
22 | ]
23 | );
24 | const dispatch = useDispatch();
25 | const { resume_id } = useParams();
26 |
27 | useEffect(() => {
28 | try {
29 | dispatch(addResumeData({ ...resumeInfo, skills: skillsList }));
30 | } catch (error) {
31 | console.log("error in experience context update", error);
32 | }
33 | }, [skillsList]);
34 |
35 | const AddNewSkills = () => {
36 | const list = [...skillsList];
37 | list.push({ name: "", rating: 0 });
38 | setSkillsList(list);
39 | };
40 |
41 | const RemoveSkills = () => {
42 | const list = [...skillsList];
43 | list.pop();
44 | setSkillsList(list);
45 | };
46 |
47 | const handleChange = (index, key, value) => {
48 | const list = [...skillsList];
49 | const newListData = {
50 | ...list[index],
51 | [key]: value,
52 | };
53 | list[index] = newListData;
54 | setSkillsList(list);
55 | };
56 |
57 | const onSave = () => {
58 | setLoading(true);
59 | const data = {
60 | data: {
61 | skills: skillsList,
62 | },
63 | };
64 |
65 | if (resume_id) {
66 | console.log("Started Updating Skills");
67 | updateThisResume(resume_id, data)
68 | .then((data) => {
69 | toast("Resume Updated", "success");
70 | })
71 | .catch((error) => {
72 | toast("Error updating resume", `${error.message}`);
73 | })
74 | .finally(() => {
75 | setLoading(false);
76 | });
77 | }
78 | };
79 | return (
80 |
81 |
Skills
82 |
Add Your top professional key skills
83 |
84 |
85 | {skillsList.map((item, index) => (
86 |
90 |
91 |
92 | handleChange(index, "name", e.target.value)}
96 | />
97 |
98 |
handleChange(index, "rating", v)}
102 | />
103 |
104 | ))}
105 |
106 |
107 |
108 |
116 |
124 |
125 |
128 |
129 |
130 | );
131 | }
132 |
133 | export default Skills;
134 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/components/ResumeCard.jsx:
--------------------------------------------------------------------------------
1 | import { FaEye, FaEdit, FaTrashAlt, FaBook, FaSpinner } from "react-icons/fa";
2 | import React from "react";
3 | import {
4 | AlertDialog,
5 | AlertDialogAction,
6 | AlertDialogCancel,
7 | AlertDialogContent,
8 | AlertDialogDescription,
9 | AlertDialogFooter,
10 | AlertDialogHeader,
11 | AlertDialogTitle,
12 | } from "@/components/ui/alert-dialog";
13 | import { Button } from "@/components/ui/button";
14 | import { deleteThisResume } from "@/Services/resumeAPI";
15 | import { toast } from "sonner";
16 | import { useNavigate } from "react-router-dom";
17 |
18 | const gradients = [
19 | "from-indigo-500 via-purple-500 to-pink-500",
20 | "from-green-400 via-blue-500 to-purple-600",
21 | "from-red-400 via-yellow-500 to-green-500",
22 | "from-blue-500 via-teal-400 to-green-300",
23 | "from-pink-500 via-red-500 to-yellow-500",
24 | ];
25 |
26 | const getRandomGradient = () => {
27 | return gradients[Math.floor(Math.random() * gradients.length)];
28 | };
29 |
30 | function ResumeCard({ resume, refreshData }) {
31 | const [loading, setLoading] = React.useState(false);
32 | const [openAlert, setOpenAlert] = React.useState(false);
33 | const gradient = getRandomGradient();
34 | const navigate = useNavigate();
35 |
36 | const handleDelete = async () => {
37 | setLoading(true);
38 | console.log("Delete Resume with ID", resume._id);
39 | try {
40 | const response = await deleteThisResume(resume._id);
41 | } catch (error) {
42 | console.error("Error deleting resume:", error.message);
43 | toast(error.message);
44 | } finally {
45 | setLoading(false);
46 | setOpenAlert(false);
47 | refreshData();
48 | }
49 | };
50 | return (
51 |
54 |
55 |
58 | {resume.title}
59 |
60 |
61 |
62 |
69 |
76 |
83 |
setOpenAlert(false)}>
84 |
85 |
86 | Are you absolutely sure?
87 |
88 | This action cannot be undone. This will permanently delete your
89 | Resume and remove your data from our servers.
90 |
91 |
92 |
93 | setOpenAlert(false)}>
94 | Cancel
95 |
96 |
97 | {loading ? : "Delete"}
98 |
99 |
100 |
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | export default ResumeCard;
108 |
--------------------------------------------------------------------------------
/Frontend/data/dummy.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | firstName: "James",
3 | lastName: "Carter",
4 | jobTitle: "full stack developer",
5 | address: "525 N tryon Street, NC 28117",
6 | phone: "(123)-456-7890",
7 | email: "exmaple@gmail.com",
8 | themeColor: "#ff6666",
9 | summary:
10 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
11 | experience: [
12 | {
13 | id: 1,
14 | title: "Full Stack Developer",
15 | companyName: "Amazon",
16 | city: "New York",
17 | state: "NY",
18 | startDate: "Jan 2021",
19 | endDate: "",
20 | currentlyWorking: true,
21 | workSummary:
22 | " Designed, developed, and maintained full-stack applications using React and Node.js.\n" +
23 | "• Implemented responsive user interfaces with React, ensuring seamless user experiences across\n" +
24 | "various devices and browsers.\n" +
25 | "• Maintaining the React Native in-house organization application." +
26 | "• CreatedRESTfulAPIs withNode.js and Express,facilitating data communicationbetween the front-end" +
27 | "and back-end systems.",
28 | },
29 | {
30 | id: 2,
31 | title: "Frontend Developer",
32 | companyName: "Google",
33 | city: "Charlotte",
34 | state: "NC",
35 | startDate: "May 2019",
36 | endDate: "Jan 2021",
37 | currentlyWorking: false,
38 | workSummary:
39 | " Designed, developed, and maintained full-stack applications using React and Node.js." +
40 | "• Implemented responsive user interfaces with React, ensuring seamless user experiences across" +
41 | "various devices and browsers." +
42 | "• Maintaining the React Native in-house organization application." +
43 | "• CreatedRESTfulAPIs withNode.js and Express,facilitating data communicationbetween the front-end" +
44 | "and back-end systems.",
45 | },
46 | ],
47 | projects: [
48 | {
49 | id: 1,
50 | projectName: "E-commerce Website",
51 | techStack: "React, Node.js, Express, MongoDB",
52 | projectSummary:
53 | "Designed and developed an e-commerce website using React and Node.js. Implemented a responsive user interface with React, ensuring seamless user experiences across various devices and browsers. Created RESTful APIs with Node.js and Express, facilitating data communication between the front-end and back-end systems.",
54 | },
55 | {
56 | id: 2,
57 | projectName: "Portfolio Website",
58 | techStack: "",
59 | projectSummary:
60 | "Designed and developed a portfolio website using React and Node.js. Implemented a responsive user interface with React, ensuring seamless user experiences across various devices and browsers. Created RESTful APIs with Node.js and Express, facilitating data communication between the front-end and back-end systems.",
61 | },
62 | ],
63 | education: [
64 | {
65 | id: 1,
66 | universityName: "Western Illinois University",
67 | startDate: "Aug 2018",
68 | endDate: "Dec:2019",
69 | degree: "Master",
70 | gradeType: "CGPA",
71 | grade: "3.5",
72 | major: "Computer Science",
73 | description:
74 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud",
75 | },
76 | {
77 | id: 2,
78 | universityName: "Western Illinois University",
79 | startDate: "Aug 2018",
80 | endDate: "Dec:2019",
81 | degree: "Master",
82 | gradeType: "CGPA",
83 | grade: "3.5",
84 | major: "Computer Science",
85 | description:
86 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud",
87 | },
88 | ],
89 | skills: [
90 | {
91 | id: 1,
92 | name: "Angular",
93 | rating: 80,
94 | },
95 | {
96 | id: 1,
97 | name: "React",
98 | rating: 100,
99 | },
100 | {
101 | id: 1,
102 | name: "MySql",
103 | rating: 80,
104 | },
105 | {
106 | id: 1,
107 | name: "React Native",
108 | rating: 100,
109 | },
110 | ],
111 | };
112 |
113 | export default data;
114 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/PersonalDetails.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch } from "react-redux";
3 | import { addResumeData } from "@/features/resume/resumeFeatures";
4 | import { Input } from "@/components/ui/input";
5 | import { Button } from "@/components/ui/button";
6 | import { useParams } from "react-router-dom";
7 | import { LoaderCircle } from "lucide-react";
8 | import { toast } from "sonner";
9 | import { updateThisResume } from "@/Services/resumeAPI";
10 |
11 | function PersonalDetails({ resumeInfo, enanbledNext }) {
12 | const { resume_id } = useParams();
13 | const dispatch = useDispatch();
14 | const [loading, setLoading] = React.useState(false);
15 | const [formData, setFormData] = React.useState({
16 | firstName: resumeInfo?.firstName || "",
17 | lastName: resumeInfo?.lastName || "",
18 | jobTitle: resumeInfo?.jobTitle || "",
19 | address: resumeInfo?.address || "",
20 | phone: resumeInfo?.phone || "",
21 | email: resumeInfo?.email || "",
22 | });
23 |
24 | const handleInputChange = (e) => {
25 | enanbledNext(false);
26 | dispatch(
27 | addResumeData({
28 | ...resumeInfo,
29 | [e.target.name]: e.target.value,
30 | })
31 | );
32 | setFormData({
33 | ...formData,
34 | [e.target.name]: e.target.value,
35 | });
36 | };
37 |
38 | const onSave = async (e) => {
39 | setLoading(true);
40 | e.preventDefault();
41 | console.log("Personal Details Save Started");
42 | const data = {
43 | data: {
44 | firstName: e.target.firstName.value,
45 | lastName: e.target.lastName.value,
46 | jobTitle: e.target.jobTitle.value,
47 | address: e.target.address.value,
48 | phone: e.target.phone.value,
49 | email: e.target.email.value,
50 | },
51 | };
52 | if (resume_id) {
53 | try {
54 | const response = await updateThisResume(resume_id, data);
55 | toast("Resume Updated", "success");
56 | } catch (error) {
57 | toast(error.message, `failed`);
58 | console.log(error.message);
59 | } finally {
60 | enanbledNext(true);
61 | setLoading(false);
62 | }
63 | }
64 | };
65 |
66 | return (
67 |
68 |
Personal Detail
69 |
Get Started with the basic information
70 |
71 |
133 |
134 | );
135 | }
136 |
137 | export default PersonalDetails;
138 |
--------------------------------------------------------------------------------
/Backend/src/controller/user.controller.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import User from "../models/user.model.js";
3 | import jwt from "jsonwebtoken";
4 | import { ApiResponse } from "../utils/ApiResponse.js";
5 | import { ApiError } from "../utils/ApiError.js";
6 |
7 | const start = async (req, res) => {
8 | if (req.user) {
9 | return res.status(200).json(new ApiResponse(200, req.user, "User Found"));
10 | } else {
11 | return res.status(404).json(new ApiResponse(404, null, "Use Not Found"));
12 | }
13 | };
14 |
15 | const registerUser = async (req, res) => {
16 | console.log("Registration Started");
17 | const { fullName, email, password } = req.body;
18 |
19 | if (!fullName || !email || !password) {
20 | console.log("Registration Failed data insufficeient ");
21 | return res
22 | .status(400)
23 | .json(
24 | new ApiError(
25 | 400,
26 | "Please provide all required fields: fullName, email, and password."
27 | )
28 | );
29 | }
30 |
31 | try {
32 | const existingUser = await User.findOne({ email });
33 |
34 | if (existingUser) {
35 | console.log("Registration Failed already registered user");
36 | return res
37 | .status(409)
38 | .json(new ApiError(409, "User already registered."));
39 | }
40 |
41 | const newUser = await User.create({
42 | fullName,
43 | email,
44 | password,
45 | });
46 |
47 | console.log("Registration Successful");
48 | res.status(201).json(
49 | new ApiResponse(
50 | 201,
51 | {
52 | user: {
53 | id: newUser._id,
54 | fullName: newUser.fullName,
55 | email: newUser.email,
56 | },
57 | },
58 | "User successfully registered."
59 | )
60 | );
61 | } catch (err) {
62 | // Change the error parameter to err
63 | console.log("Registration Failed due to server error");
64 | console.error("Error while creating user:", err);
65 | return res
66 | .status(500)
67 | .json(new ApiError(500, "Internal Server Error.", [], err.stack));
68 | }
69 | // return res.status(200).send("Hello WOrld")
70 | };
71 |
72 | const loginUser = async (req, res) => {
73 | console.log("Login Started");
74 | const { email, password } = req.body;
75 |
76 | if (!email || !password) {
77 | console.log("Login Failed: Missing required fields");
78 | return res
79 | .status(400)
80 | .json(
81 | new ApiError(
82 | 400,
83 | "Please provide all required fields: email and password."
84 | )
85 | );
86 | }
87 |
88 | try {
89 | const user = await User.findOne({ email });
90 |
91 | if (!user) {
92 | console.log("Login Failed: User not found");
93 | return res.status(404).json(new ApiError(404, "User not found."));
94 | }
95 |
96 | const isPasswordCorrect = await user.comparePassword(password);
97 |
98 | if (!isPasswordCorrect) {
99 | console.log("Login Failed: Invalid credentials");
100 | return res.status(406).json(new ApiError(406, "Invalid credentials."));
101 | }
102 |
103 | const jwtToken = jwt.sign(
104 | { id: user.id },
105 | process.env.JWT_SECRET_KEY,
106 | { expiresIn: process.env.JWT_SECRET_EXPIRES_IN } // Add token expiration for better security
107 | );
108 |
109 | const cookieOptions = {
110 | httpOnly: true,
111 | expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // Set cookie to expire in 1 day
112 | sameSite: process.env.NODE_ENV == "Dev" ? "lax" : "none", // Set SameSite attribute for better security
113 | secure: process.env.NODE_ENV == "Dev" ? false : true, // Set Secure attribute for better security
114 | };
115 |
116 |
117 | console.log("Login Successful");
118 | return res
119 | .cookie("token", jwtToken, cookieOptions)
120 | .status(200)
121 | .json(
122 | new ApiResponse(
123 | 200,
124 | {
125 | user: {
126 | id: user._id,
127 | email: user.email,
128 | fullName: user.fullName,
129 | },
130 | },
131 | "User logged in successfully."
132 | )
133 | );
134 | } catch (err) {
135 | console.log("Login Failed: Server error");
136 | console.error("Error during login:", err);
137 | return res
138 | .status(500)
139 | .json(new ApiError(500, "Internal Server Error", [], err.stack));
140 | }
141 | };
142 |
143 | const logoutUser = (req, res) => {
144 | console.log("Logged out");
145 | if (req.user) {
146 | return res
147 | .clearCookie("token")
148 | .status(200)
149 | .json(new ApiResponse(200, null, "User logged out successfully."));
150 | } else {
151 | return res.status(404).json(new ApiError(404, "User not found."));
152 | }
153 | };
154 |
155 | export { start, loginUser, logoutUser, registerUser };
156 |
--------------------------------------------------------------------------------
/Frontend/src/pages/home/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import Header from "@/components/custom/Header";
2 | import React, { useEffect } from "react";
3 | import heroSnapshot from "@/assets/heroSnapshot.png";
4 | import { useNavigate } from "react-router-dom";
5 | import { FaGithub, FaCircle, FaInfoCircle } from "react-icons/fa";
6 | import { Button } from "@/components/ui/button";
7 | import { startUser } from "../../Services/login.js";
8 | import { useDispatch, useSelector } from "react-redux";
9 | import { addUserData } from "@/features/user/userFeatures.js";
10 |
11 | function HomePage() {
12 | const user = useSelector((state) => state.editUser.userData);
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const handleClick = () => {
16 | window.open(
17 | "https://github.com/sahidrajaansari/Ai-Resume-Builder",
18 | "_blank"
19 | );
20 | };
21 |
22 | useEffect(() => {
23 | const fetchResponse = async () => {
24 | try {
25 | const response = await startUser();
26 | if (response.statusCode == 200) {
27 | dispatch(addUserData(response.data));
28 | } else {
29 | dispatch(addUserData(""));
30 | }
31 | } catch (error) {
32 | console.log(
33 | "Printing from Home Page there was a error ->",
34 | error.message
35 | );
36 | dispatch(addUserData(""));
37 | }
38 | };
39 | fetchResponse();
40 | }, []);
41 |
42 | const hadnleGetStartedClick = () => {
43 | if (user) {
44 | console.log("Printing from Homepage User is There ");
45 | navigate("/dashboard");
46 | } else {
47 | console.log("Printing for Homepage User Not Found");
48 | navigate("/auth/sign-in");
49 | }
50 | };
51 | return (
52 | <>
53 |
54 |
55 |
56 |
57 |
58 | Start{" "}
59 |
60 | building a Resume
61 | {" "}
62 | for your next Job
63 |
64 |
65 | Build. Refine. Shine. With AI-Driven Resumes
66 |
67 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |

104 |
105 |
106 |
107 |
108 |
109 |
119 | >
120 | );
121 | }
122 |
123 | export default HomePage;
124 |
--------------------------------------------------------------------------------
/Backend/src/controller/resume.controller.js:
--------------------------------------------------------------------------------
1 | import { ApiError } from "../utils/ApiError.js";
2 | import { ApiResponse } from "../utils/ApiResponse.js";
3 | import Resume from "../models/resume.model.js";
4 |
5 | const start = async (req, res) => {
6 | return res
7 | .status(200)
8 | .json(new ApiResponse(200, null, "Welcome to Resume Builder API"));
9 | };
10 |
11 | const createResume = async (req, res) => {
12 | const { title, themeColor } = req.body;
13 |
14 | // Validate that the title and themeColor are provided
15 | if (!title || !themeColor) {
16 | return res
17 | .status(400)
18 | .json(new ApiError(400, "Title and themeColor are required."));
19 | }
20 |
21 | try {
22 | // Create a new resume with empty fields for other attributes
23 | const resume = await Resume.create({
24 | title,
25 | themeColor,
26 | user: req.user._id, // Set the user ID from the authenticated user
27 | firstName: "",
28 | lastName: "",
29 | email: "",
30 | summary: "",
31 | jobTitle: "",
32 | phone: "",
33 | address: "",
34 | experience: [],
35 | education: [], // Initialize as an empty array
36 | skills: [],
37 | projects: [],
38 | });
39 |
40 | return res
41 | .status(201)
42 | .json(new ApiResponse(201, { resume }, "Resume created successfully"));
43 | } catch (error) {
44 | console.error("Error creating resume:", error);
45 | return res
46 | .status(500)
47 | .json(
48 | new ApiError(500, "Internal Server Error", [error.message], error.stack)
49 | );
50 | }
51 | };
52 |
53 | const getALLResume = async (req, res) => {
54 | try {
55 | const resumes = await Resume.find({ user: req.user });
56 | return res
57 | .status(200)
58 | .json(new ApiResponse(200, resumes, "Resumes fetched successfully"));
59 | } catch (error) {
60 | console.error("Error fetching resumes:", error);
61 | return res
62 | .status(500)
63 | .json(new ApiError(500, "Internal Server Error", [], error.stack));
64 | }
65 | };
66 |
67 | const getResume = async (req, res) => {
68 | try {
69 | const { id } = req.query;
70 |
71 | if (!id) {
72 | return res.status(400).json(new ApiError(400, "Resume ID is required."));
73 | }
74 |
75 | // Find the resume by ID
76 | const resume = await Resume.findById(id);
77 |
78 | if (!resume) {
79 | return res.status(404).json(new ApiError(404, "Resume not found."));
80 | }
81 |
82 | // Check if the resume belongs to the current user
83 | if (resume.user.toString() !== req.user._id.toString()) {
84 | return res
85 | .status(403)
86 | .json(
87 | new ApiError(403, "You are not authorized to access this resume.")
88 | );
89 | }
90 |
91 | return res
92 | .status(200)
93 | .json(new ApiResponse(200, resume, "Resume fetched successfully"));
94 | } catch (error) {
95 | console.error("Error fetching resume:", error);
96 | return res
97 | .status(500)
98 | .json(new ApiError(500, "Internal Server Error", [], error.stack));
99 | }
100 | };
101 |
102 | const updateResume = async (req, res) => {
103 | console.log("Resume update request received:");
104 | const id = req.query.id;
105 |
106 | try {
107 | // Find and update the resume with the provided ID and user ID
108 | console.log("Database update request started");
109 | const updatedResume = await Resume.findOneAndUpdate(
110 | { _id: id, user: req.user._id },
111 | { $set: req.body, $currentDate: { updatedAt: true } }, // Set updatedAt to current date
112 | { new: true } // Return the modified document
113 | );
114 |
115 | if (!updatedResume) {
116 | console.log("Resume not found or unauthorized");
117 | return res
118 | .status(404)
119 | .json(new ApiResponse(404, null, "Resume not found or unauthorized"));
120 | }
121 |
122 | console.log("Resume updated successfully:");
123 |
124 | return res
125 | .status(200)
126 | .json(new ApiResponse(200, updatedResume, "Resume updated successfully"));
127 | } catch (error) {
128 | console.error("Error updating resume:", error);
129 | return res
130 | .status(500)
131 | .json(
132 | new ApiError(500, "Internal Server Error", [error.message], error.stack)
133 | );
134 | }
135 |
136 | // return res.status(200).json({ message: "Hello World" });
137 | };
138 |
139 | const removeResume = async (req, res) => {
140 | const id = req.query.id;
141 |
142 | try {
143 | // Check if the resume exists and belongs to the current user
144 | const resume = await Resume.findOneAndDelete({
145 | _id: id,
146 | user: req.user._id,
147 | });
148 |
149 | if (!resume) {
150 | return res
151 | .status(404)
152 | .json(
153 | new ApiResponse(
154 | 404,
155 | null,
156 | "Resume not found or not authorized to delete this resume"
157 | )
158 | );
159 | }
160 |
161 | return res
162 | .status(200)
163 | .json(new ApiResponse(200, null, "Resume deleted successfully"));
164 | } catch (error) {
165 | console.error("Error while deleting resume:", error);
166 | return res
167 | .status(500)
168 | .json(new ApiResponse(500, null, "Internal Server Error"));
169 | }
170 | };
171 |
172 | export {
173 | start,
174 | createResume,
175 | getALLResume,
176 | getResume,
177 | updateResume,
178 | removeResume,
179 | };
180 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/Project.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Button } from "@/components/ui/button";
3 | import { Trash2 } from "lucide-react";
4 | import { Input } from "@/components/ui/input";
5 | import SimpeRichTextEditor from "@/components/custom/SimpeRichTextEditor";
6 | import { useDispatch } from "react-redux";
7 | import { addResumeData } from "@/features/resume/resumeFeatures";
8 | import { toast } from "sonner";
9 | import { useParams } from "react-router-dom";
10 | import { LoaderCircle } from "lucide-react";
11 | import { updateThisResume } from "@/Services/resumeAPI";
12 |
13 | const formFields = {
14 | projectName: "",
15 | techStack: "",
16 | projectSummary: "",
17 | };
18 | function Project({ resumeInfo, setEnabledNext, setEnabledPrev }) {
19 | const [projectList, setProjectList] = useState(resumeInfo?.projects || []);
20 | const [loading, setLoading] = useState(false);
21 | const { resume_id } = useParams();
22 | const dispatch = useDispatch();
23 |
24 | useEffect(() => {
25 | dispatch(addResumeData({ ...resumeInfo, projects: projectList }));
26 | }, [projectList]);
27 |
28 | const addProject = () => {
29 | setProjectList([...projectList, formFields]);
30 | };
31 |
32 | const removeProject = (index) => {
33 | const list = [...projectList];
34 | const newList = list.filter((item, i) => {
35 | if (i !== index) return true;
36 | });
37 | setProjectList(newList);
38 | };
39 |
40 | const handleChange = (e, index) => {
41 | setEnabledNext(false);
42 | setEnabledPrev(false);
43 | console.log("Type: ", typeof setEnabledPrev);
44 | const { name, value } = e.target;
45 | const list = [...projectList];
46 | const newListData = {
47 | ...list[index],
48 | [name]: value,
49 | };
50 | list[index] = newListData;
51 | setProjectList(list);
52 | };
53 |
54 | const handleRichTextEditor = (value, name, index) => {
55 | const list = [...projectList];
56 | const newListData = {
57 | ...list[index],
58 | [name]: value,
59 | };
60 | list[index] = newListData;
61 | setProjectList(list);
62 | };
63 |
64 | const onSave = () => {
65 | setLoading(true);
66 | const data = {
67 | data: {
68 | projects: projectList,
69 | },
70 | };
71 | if (resume_id) {
72 | console.log("Started Updating Project");
73 | updateThisResume(resume_id, data)
74 | .then((data) => {
75 | toast("Resume Updated", "success");
76 | })
77 | .catch((error) => {
78 | toast("Error updating resume", `${error.message}`);
79 | })
80 | .finally(() => {
81 | setEnabledNext(true);
82 | setEnabledPrev(true);
83 | setLoading(false);
84 | });
85 | }
86 | };
87 |
88 | return (
89 |
90 |
Project
91 |
Add your projects
92 |
93 | {projectList?.map((project, index) => (
94 |
95 |
96 |
Project {index + 1}
97 |
106 |
107 |
108 |
109 |
110 | {
115 | handleChange(e, index);
116 | }}
117 | />
118 |
119 |
120 |
121 | {
127 | handleChange(e, index);
128 | }}
129 | />
130 |
131 |
132 |
136 | handleRichTextEditor(event, "projectSummary", index)
137 | }
138 | resumeInfo={resumeInfo}
139 | />
140 |
141 |
142 |
143 | ))}
144 |
145 |
146 |
149 |
152 |
153 |
154 | );
155 | }
156 |
157 | export default Project;
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 AI Resume Builder
2 |
3 |
4 |

5 |
6 | [](https://reactjs.org/)
7 | [](https://vitejs.dev/)
8 | [](https://nodejs.org/)
9 | [](https://www.mongodb.com/)
10 | [](https://tailwindcss.com/)
11 | [](LICENSE)
12 |
13 |
14 | ## 📋 Overview
15 |
16 | A modern AI-powered resume builder that helps users create professional resumes with ease. Leveraging Google's Gemini AI, this application provides intelligent suggestions and automated content generation to make resume creation effortless and professional.
17 |
18 | ## 🎯 Project Objectives
19 |
20 | 1. **AI-Powered Content Generation**
21 | - Smart summary generation based on job titles
22 | - Intelligent experience descriptions
23 | - Automated project descriptions
24 | - Context-aware content suggestions
25 |
26 | 2. **Professional Resume Creation**
27 | - Modern, clean templates
28 | - Customizable sections
29 | - Real-time preview
30 | - Multiple format support
31 |
32 | 3. **User Experience**
33 | - Intuitive interface
34 | - Step-by-step guidance
35 | - Easy content editing
36 | - Instant AI suggestions
37 |
38 | ## 🌟 Key Features
39 |
40 |
41 |
42 |
43 |
44 | 🤖 AI Integration
45 |
46 | - Gemini AI powered
47 | - Smart suggestions
48 | - Content generation
49 | - Context awareness
50 |
51 |
52 | |
53 |
54 |
55 | 📝 Resume Building
56 |
57 | - Multiple sections
58 | - Custom templates
59 | - Real-time preview
60 | - Easy editing
61 |
62 |
63 | |
64 |
65 |
66 | 💾 Data Management
67 |
68 | - Secure storage
69 | - Easy updates
70 | - Version control
71 | - Data backup
72 |
73 |
74 | |
75 |
76 |
77 | 🎨 Customization
78 |
79 | - Color themes
80 | - Font selection
81 | - Layout options
82 | - Section ordering
83 |
84 |
85 | |
86 |
87 |
88 |
89 | ## 🛠️ Technical Stack
90 |
91 |
92 |

93 |

94 |

95 |

96 |

97 |

98 |
99 |
100 | ## 🚀 Getting Started
101 |
102 | ### Prerequisites
103 | - Node.js 20.0.0 or higher
104 | - MongoDB
105 | - Google Gemini API Key
106 |
107 | ### Installation
108 |
109 | 1. Clone the repository
110 | ```bash
111 | git clone https://github.com/yourusername/Ai-Resume-Builder.git
112 | cd Ai-Resume-Builder
113 | ```
114 |
115 | 2. Install backend dependencies
116 | ```bash
117 | cd Backend
118 | npm install
119 | ```
120 |
121 | 3. Install frontend dependencies
122 | ```bash
123 | cd Frontend
124 | npm install
125 | ```
126 |
127 | 4. Set up environment variables
128 | ```bash
129 | # Frontend/.env.local
130 | VITE_GEMENI_API_KEY=your-gemini-api-key
131 | VITE_APP_URL=http://localhost:5001/
132 |
133 | # Backend/.env
134 | MONGODB_URI=your-mongodb-uri
135 | PORT=5001
136 | ```
137 |
138 | 5. Start the backend server
139 | ```bash
140 | cd Backend
141 | npm start
142 | ```
143 |
144 | 6. Start the frontend development server
145 | ```bash
146 | cd Frontend
147 | npm run dev
148 | ```
149 |
150 | 7. Access the application at `http://localhost:5173`
151 |
152 | ## 📁 Project Structure
153 |
154 | ```
155 | Ai-Resume-Builder/
156 | ├── Frontend/ # React frontend application
157 | │ ├── src/
158 | │ │ ├── components/ # Reusable components
159 | │ │ ├── pages/ # Page components
160 | │ │ ├── services/ # API services
161 | │ │ └── features/ # Redux features
162 | │ └── public/ # Static assets
163 | ├── Backend/ # Node.js backend server
164 | │ ├── controllers/ # Route controllers
165 | │ ├── models/ # Database models
166 | │ ├── routes/ # API routes
167 | │ └── services/ # Business logic
168 | └── README.md # Project documentation
169 | ```
170 |
171 | ## 🔐 Security Features
172 |
173 | - JWT Authentication
174 | - Secure API endpoints
175 | - Environment variable protection
176 | - Input validation
177 | - Error handling
178 |
179 | ## 🌐 Performance Optimization
180 |
181 | - Code splitting
182 | - Lazy loading
183 | - Image optimization
184 | - Caching strategies
185 | - API response optimization
186 |
187 | ## 📈 Future Roadmap
188 |
189 | - [ ] Additional resume templates
190 | - [ ] PDF export improvements
191 | - [ ] Enhanced AI suggestions
192 | - [ ] Multi-language support
193 | - [ ] ATS optimization
194 | - [ ] LinkedIn integration
195 | - [ ] Mobile app version
196 | - [ ] Advanced customization options
197 |
198 | ## 🤝 Contributing
199 |
200 | Contributions are welcome! Please feel free to submit a Pull Request.
201 |
202 | ## 📄 License
203 |
204 | This project is licensed under the MIT License.
205 |
206 | ## 📞 Contact
207 |
208 | For any queries or support, please contact the team members:
209 | - GitHub: [Project Repository](https://github.com/yourusername/Ai-Resume-Builder)
210 |
211 | ---
212 |
213 |
214 |
© 2024 AI Resume Builder
215 |
216 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/Education.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Button } from "@/components/ui/button";
3 | import { Input } from "@/components/ui/input";
4 | import { Textarea } from "@/components/ui/textarea";
5 | import { LoaderCircle } from "lucide-react";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { addResumeData } from "@/features/resume/resumeFeatures";
8 | import { useParams } from "react-router-dom";
9 | import { updateResumeData } from "@/Services/GlobalApi";
10 | import { toast } from "sonner";
11 | import { updateThisResume } from "@/Services/resumeAPI";
12 |
13 | const formFields = {
14 | universityName: "",
15 | degree: "",
16 | major: "",
17 | grade: "",
18 | gradeType: "CGPA",
19 | startDate: "",
20 | endDate: "",
21 | description: "",
22 | };
23 | function Education({ resumeInfo, enanbledNext }) {
24 | const [educationalList, setEducationalList] = React.useState(
25 | resumeInfo?.education || [{ ...formFields }]
26 | );
27 | const { resume_id } = useParams();
28 | const dispatch = useDispatch();
29 | const [loading, setLoading] = React.useState(false);
30 |
31 | useEffect(() => {
32 | dispatch(addResumeData({ ...resumeInfo, education: educationalList }));
33 | }, [educationalList]);
34 |
35 | const AddNewEducation = () => {
36 | setEducationalList([...educationalList, { ...formFields }]);
37 | };
38 |
39 | const RemoveEducation = () => {
40 | setEducationalList((educationalList) => educationalList.slice(0, -1));
41 | };
42 |
43 | const onSave = () => {
44 | if (educationalList.length === 0) {
45 | return toast("Please add atleast one education", "error");
46 | }
47 | setLoading(true);
48 | const data = {
49 | data: {
50 | education: educationalList,
51 | },
52 | };
53 | if (resume_id) {
54 | console.log("Started Updating Education");
55 | updateThisResume(resume_id, data)
56 | .then((data) => {
57 | toast("Resume Updated", "success");
58 | })
59 | .catch((error) => {
60 | toast("Error updating resume", `${error.message}`);
61 | })
62 | .finally(() => {
63 | setLoading(false);
64 | });
65 | }
66 | };
67 |
68 | const handleChange = (e, index) => {
69 | const { name, value } = e.target;
70 | const list = [...educationalList];
71 | const newListData = {
72 | ...list[index],
73 | [name]: value,
74 | };
75 | list[index] = newListData;
76 | setEducationalList(list);
77 | };
78 |
79 | return (
80 |
81 |
Education
82 |
Add Your educational details
83 |
84 |
85 | {educationalList.map((item, index) => (
86 |
87 |
88 |
89 |
90 | handleChange(e, index)}
93 | defaultValue={item?.universityName}
94 | />
95 |
96 |
97 |
98 | handleChange(e, index)}
101 | defaultValue={item?.degree}
102 | />
103 |
104 |
105 |
106 | handleChange(e, index)}
109 | defaultValue={item?.major}
110 | />
111 |
112 |
113 |
114 | handleChange(e, index)}
118 | defaultValue={item?.startDate}
119 | />
120 |
121 |
122 |
123 | handleChange(e, index)}
127 | defaultValue={item?.endDate}
128 | />
129 |
130 |
131 |
132 |
133 |
143 | handleChange(e, index)}
147 | defaultValue={item?.endDate}
148 | />
149 |
150 |
151 |
152 |
153 |
159 |
160 |
161 | ))}
162 |
163 |
164 |
165 |
173 |
181 |
182 |
185 |
186 |
187 | );
188 | }
189 |
190 | export default Education;
191 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/Summary.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Sparkles, LoaderCircle } from "lucide-react";
3 | import { Button } from "@/components/ui/button";
4 | import { Textarea } from "@/components/ui/textarea";
5 | import { useDispatch } from "react-redux";
6 | import { addResumeData } from "@/features/resume/resumeFeatures";
7 | import { useParams } from "react-router-dom";
8 | import { toast } from "sonner";
9 | import { AIChatSession } from "@/Services/AiModel";
10 | import { updateThisResume } from "@/Services/resumeAPI";
11 |
12 | const prompt =
13 | "Job Title: {jobTitle} , Depends on job title give me list of summery for 3 experience level, Mid Level and Freasher level in 3 -4 lines in array format, With summery and experience_level Field in JSON Format";
14 | function Summary({ resumeInfo, enanbledNext, enanbledPrev }) {
15 | const dispatch = useDispatch();
16 | const [loading, setLoading] = useState(false);
17 | const [summary, setSummary] = useState(resumeInfo?.summary || "");
18 | const [aiGeneratedSummeryList, setAiGenerateSummeryList] = useState(null);
19 | const { resume_id } = useParams();
20 |
21 | const handleInputChange = (e) => {
22 | enanbledNext(false);
23 | enanbledPrev(false);
24 | dispatch(
25 | addResumeData({
26 | ...resumeInfo,
27 | [e.target.name]: e.target.value,
28 | })
29 | );
30 | setSummary(e.target.value);
31 | };
32 |
33 | const onSave = (e) => {
34 | e.preventDefault();
35 | setLoading(true);
36 | console.log("Started Saving Summary");
37 | const data = {
38 | data: { summary },
39 | };
40 | if (resume_id) {
41 | updateThisResume(resume_id, data)
42 | .then((data) => {
43 | toast.success("Resume Updated");
44 | })
45 | .catch((error) => {
46 | toast.error("Error updating resume", `${error.message}`);
47 | })
48 | .finally(() => {
49 | enanbledNext(true);
50 | enanbledPrev(true);
51 | setLoading(false);
52 | });
53 | }
54 | };
55 |
56 | const setSummery = (summary) => {
57 | dispatch(
58 | addResumeData({
59 | ...resumeInfo,
60 | summary: summary,
61 | })
62 | );
63 | setSummary(summary);
64 | };
65 |
66 | const GenerateSummeryFromAI = async () => {
67 | setLoading(true);
68 | console.log("Generate Summery From AI for", resumeInfo?.jobTitle);
69 | if (!resumeInfo?.jobTitle) {
70 | toast.error("Please Add Job Title");
71 | setLoading(false);
72 | return;
73 | }
74 | const PROMPT = prompt.replace("{jobTitle}", resumeInfo?.jobTitle);
75 | try {
76 | const result = await AIChatSession.sendMessage(PROMPT);
77 | const response = await result.response.text();
78 | console.log("AI Response:", response);
79 |
80 | try {
81 | let parsedResponse = JSON.parse(response);
82 |
83 | // Handle both array and object responses
84 | if (!Array.isArray(parsedResponse)) {
85 | // If it's an object with a data/items/summaries field, try to extract the array
86 | parsedResponse = parsedResponse.data || parsedResponse.items || parsedResponse.summaries || [parsedResponse];
87 | }
88 |
89 | // Ensure each item has the required fields
90 | const validResponse = Array.isArray(parsedResponse) && parsedResponse.every(item =>
91 | item && typeof item === 'object' &&
92 | (item.summary || item.description) &&
93 | (item.experience_level || item.level)
94 | );
95 |
96 | if (validResponse) {
97 | // Normalize the response format
98 | const normalizedResponse = parsedResponse.map(item => ({
99 | summary: item.summary || item.description,
100 | experience_level: item.experience_level || item.level
101 | }));
102 |
103 | setAiGenerateSummeryList(normalizedResponse);
104 | toast.success("Summary Generated Successfully");
105 | } else {
106 | throw new Error("Invalid response format - missing required fields");
107 | }
108 | } catch (parseError) {
109 | console.error("Parse error:", parseError, "\nResponse:", response);
110 | toast.error("Failed to parse AI response. Please try again.");
111 | }
112 | } catch (error) {
113 | console.error("AI error:", error);
114 | toast.error("Failed to generate summary. Please check your job title and try again.");
115 | } finally {
116 | setLoading(false);
117 | }
118 | };
119 |
120 | return (
121 |
122 |
123 |
Summary
124 |
Add Summary for your job title
125 |
126 |
154 |
155 |
156 | {aiGeneratedSummeryList && Array.isArray(aiGeneratedSummeryList) && aiGeneratedSummeryList.length > 0 && (
157 |
158 |
Suggestions
159 | {aiGeneratedSummeryList.map((item, index) => (
160 |
{
163 | enanbledNext(false);
164 | enanbledPrev(false);
165 | setSummery(item?.summary);
166 | }}
167 | className="p-5 shadow-lg my-4 rounded-lg cursor-pointer hover:bg-gray-50"
168 | >
169 |
170 | Level: {item?.experience_level}
171 |
172 |
{item?.summary}
173 |
174 | ))}
175 |
176 | )}
177 |
178 | );
179 | }
180 |
181 | export default Summary;
182 |
--------------------------------------------------------------------------------
/Frontend/src/pages/dashboard/edit-resume/components/form-components/Experience.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button";
2 | import { Input } from "@/components/ui/input";
3 | import { LoaderCircle, Trash2 } from "lucide-react";
4 | import RichTextEditor from "@/components/custom/RichTextEditor";
5 | import React, { useEffect } from "react";
6 | import { useDispatch } from "react-redux";
7 | import { addResumeData } from "@/features/resume/resumeFeatures";
8 | import { useParams } from "react-router-dom";
9 | import { updateResumeData } from "@/Services/GlobalApi";
10 | import { updateThisResume } from "@/Services/resumeAPI";
11 | import { toast } from "sonner";
12 |
13 | const formFields = {
14 | title: "",
15 | companyName: "",
16 | city: "",
17 | state: "",
18 | startDate: "",
19 | endDate: "",
20 | currentlyWorking: "",
21 | workSummary: "",
22 | };
23 | function Experience({ resumeInfo, enanbledNext, enanbledPrev }) {
24 | const [experienceList, setExperienceList] = React.useState(
25 | resumeInfo?.experience || []
26 | );
27 | const [loading, setLoading] = React.useState(false);
28 | const { resume_id } = useParams();
29 |
30 | const dispatch = useDispatch();
31 |
32 | useEffect(() => {
33 | try {
34 | dispatch(addResumeData({ ...resumeInfo, experience: experienceList }));
35 | } catch (error) {
36 | console.log("error in experience context update", error.message);
37 | }
38 | }, [experienceList]);
39 |
40 | const addExperience = () => {
41 | if (!experienceList) {
42 | setExperienceList([formFields]);
43 | return;
44 | }
45 | setExperienceList([...experienceList, formFields]);
46 | };
47 |
48 | const removeExperience = (index) => {
49 | const list = [...experienceList];
50 | const newList = list.filter((item, i) => {
51 | if (i !== index) return true;
52 | });
53 | setExperienceList(newList);
54 | };
55 |
56 | const handleChange = (e, index) => {
57 | enanbledNext(false);
58 | enanbledPrev(false);
59 | const { name, value } = e.target;
60 | const list = [...experienceList];
61 | const newListData = {
62 | ...list[index],
63 | [name]: value,
64 | };
65 | list[index] = newListData;
66 | setExperienceList(list);
67 | };
68 |
69 | const handleRichTextEditor = (value, name, index) => {
70 | const list = [...experienceList];
71 | const newListData = {
72 | ...list[index],
73 | [name]: value,
74 | };
75 | list[index] = newListData;
76 | setExperienceList(list);
77 | };
78 |
79 | const onSave = () => {
80 | setLoading(true);
81 | const data = {
82 | data: {
83 | experience: experienceList,
84 | },
85 | };
86 | if (resume_id) {
87 | console.log("Started Updating Experience");
88 | updateThisResume(resume_id, data)
89 | .then((data) => {
90 | toast("Resume Updated", "success");
91 | })
92 | .catch((error) => {
93 | toast("Error updating resume", `${error.message}`);
94 | })
95 | .finally(() => {
96 | enanbledNext(true);
97 | enanbledPrev(true);
98 | setLoading(false);
99 | });
100 | }
101 | };
102 | return (
103 |
104 |
105 |
Experience
106 |
Add Your Previous Job Experience
107 |
108 | {experienceList?.map((experience, index) => (
109 |
110 |
111 |
Experience {index + 1}
112 |
121 |
122 |
200 |
201 | ))}
202 |
203 |
204 |
212 |
215 |
216 |
217 |
218 | );
219 | }
220 |
221 | export default Experience;
222 |
--------------------------------------------------------------------------------
/Frontend/src/pages/auth/customAuth/AuthPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | FaUser,
4 | FaLock,
5 | FaSignInAlt,
6 | FaUserPlus,
7 | FaEye,
8 | FaEyeSlash,
9 | } from "react-icons/fa";
10 | import { motion } from "framer-motion";
11 | import { loginUser, registerUser } from "@/Services/login";
12 | import { Loader2 } from "lucide-react";
13 | import { useNavigate } from "react-router-dom";
14 |
15 | function AuthPage() {
16 | const [isSignUp, setIsSignUp] = useState(false);
17 | const [showPassword, setShowPassword] = useState(false);
18 | const [signUpError, setSignUpError] = useState("");
19 | const [email, setEmail] = useState("");
20 | const [password, setPassword] = useState("");
21 | const [signInError, setSignInError] = useState("");
22 | const [loading, setLoading] = useState(false);
23 | const navigate = useNavigate();
24 |
25 | const handleSignInSubmit = async (event) => {
26 | setSignInError("");
27 | event.preventDefault();
28 | const { email, password } = event.target.elements;
29 |
30 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
31 | if (!emailPattern.test(email.value)) {
32 | setError("Please enter a valid email address.");
33 | return;
34 | }
35 |
36 | setLoading(true);
37 | const data = {
38 | email: email.value,
39 | password: password.value,
40 | };
41 |
42 | try {
43 | console.log("Login Started in Frontend");
44 | const user = await loginUser(data);
45 | console.log("Login Completed");
46 |
47 | if (user?.statusCode === 200) {
48 | navigate("/");
49 | }
50 | console.log(user);
51 | } catch (error) {
52 | setSignInError(error.message);
53 | console.log("Login Failed");
54 | } finally {
55 | setLoading(false);
56 | }
57 | };
58 |
59 | const handleSignUpSubmit = async (event) => {
60 | setSignUpError("");
61 | event.preventDefault();
62 | const { fullname, email, password } = event.target.elements;
63 |
64 | // Simple email validation
65 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
66 | if (!emailPattern.test(email.value)) {
67 | setError("Please enter a valid email address.");
68 | return;
69 | }
70 |
71 | setLoading(true);
72 | console.log("User Registration Started");
73 | const data = {
74 | fullName: fullname.value,
75 | email: email.value,
76 | password: password.value,
77 | };
78 | try {
79 | const response = await registerUser(data);
80 | if (response?.statusCode === 201) {
81 | console.log("User Registration Started");
82 | handleSignInSubmit(event);
83 | }
84 | } catch (error) {
85 | console.log("User Registration Failed");
86 | setSignUpError(error.message);
87 | } finally {
88 | setLoading(false);
89 | }
90 | };
91 |
92 | return (
93 |
94 |
100 |
101 |
110 |
119 |
120 |
121 |
251 |
252 |
253 | {isSignUp ? (
254 | <>
255 | Already have an account?{" "}
256 |
262 | >
263 | ) : (
264 | <>
265 | Don’t have an account?{" "}
266 |
272 | >
273 | )}
274 |
275 |
276 |
277 | );
278 | }
279 |
280 | export default AuthPage;
281 |
--------------------------------------------------------------------------------