├── .env.exampe
├── preview.png
├── preview2.png
├── public
└── assets
│ ├── logo.png
│ └── resume.jpg
├── next.config.mjs
├── pages
├── _app.jsx
├── _document.jsx
├── index.jsx
├── api
│ ├── hello.ts
│ └── suggestions.js
├── templates.jsx
└── builder.jsx
├── postcss.config.mjs
├── lib
└── utils.ts
├── components
├── preview
│ ├── Language.jsx
│ ├── Certification.jsx
│ ├── ContactInfo.jsx
│ ├── Skills.jsx
│ ├── TemplateTwo.jsx
│ └── Preview.jsx
├── utility
│ ├── DateRange.jsx
│ ├── WinPrint.js
│ └── DefaultResumeData.jsx
├── form
│ ├── FormButton.jsx
│ ├── FormCP.jsx
│ ├── Summary.jsx
│ ├── Language.jsx
│ ├── PersonalInformation.jsx
│ ├── certification.jsx
│ ├── SocialMedia.jsx
│ ├── LoadUnload.jsx
│ ├── Skill.jsx
│ ├── Education.jsx
│ ├── Projects.jsx
│ └── WorkExperience.jsx
├── ai
│ ├── AIAnalysis.jsx
│ └── AISuggestionButton.jsx
├── meta
│ └── Meta.js
├── hero
│ └── Hero.jsx
└── ui
│ └── sparkles.tsx
├── .gitignore
├── tsconfig.json
├── tailwind.config.ts
├── package.json
├── styles
└── globals.css
├── utils
└── gemini.js
├── README.md
└── LICENSE
/.env.exampe:
--------------------------------------------------------------------------------
1 | GEMINI_API_KEY=AIzaSyB0000000000000000000000000000000
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HOTHEAD01TH/free-resume-maker/HEAD/preview.png
--------------------------------------------------------------------------------
/preview2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HOTHEAD01TH/free-resume-maker/HEAD/preview2.png
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HOTHEAD01TH/free-resume-maker/HEAD/public/assets/logo.png
--------------------------------------------------------------------------------
/public/assets/resume.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HOTHEAD01TH/free-resume-maker/HEAD/public/assets/resume.jpg
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import '/styles/globals.css'
2 |
3 | export default function App({ Component, pageProps }) {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/components/preview/Language.jsx:
--------------------------------------------------------------------------------
1 | const Language = ({ title, languages }) => {
2 | return (
3 | languages.length > 0 && (
4 |
5 |
6 | {title}
7 |
8 |
{languages.join(", ")}
9 |
10 | )
11 | );
12 | };
13 |
14 | export default Language;
--------------------------------------------------------------------------------
/components/utility/DateRange.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | const DateRange = ({ startYear, endYear, id }) => {
4 | const start = new Date(startYear);
5 | const end = new Date(endYear);
6 | return (
7 |
8 | {start.toLocaleString('default', { month: 'short' })}, {start.getFullYear()} - {end != "Invalid Date" ? end.toLocaleString('default', { month: 'short' }) + ', ' + end.getFullYear() : 'Present'}
9 |
10 | );
11 | };
12 |
13 | export default DateRange;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 | .env
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/components/utility/WinPrint.js:
--------------------------------------------------------------------------------
1 | import { MdPictureAsPdf } from "react-icons/md";
2 |
3 | const WinPrint = () => {
4 |
5 | const print = () => {
6 | window.print();
7 | };
8 |
9 | return (
10 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default WinPrint;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/components/preview/Certification.jsx:
--------------------------------------------------------------------------------
1 | const Certification = ({ title, certifications }) => {
2 | if (!certifications || certifications.length === 0) {
3 | return null;
4 | }
5 |
6 | return (
7 |
8 |
{title}
9 |
10 | {certifications.map((certification, index) => (
11 |
12 | {certification.name}
13 | {certification.issuer && (
14 | - {certification.issuer}
15 | )}
16 |
17 | ))}
18 |
19 |
20 | );
21 | };
22 |
23 | export default Certification;
--------------------------------------------------------------------------------
/components/form/FormButton.jsx:
--------------------------------------------------------------------------------
1 | import { MdAddCircle, MdRemoveCircle } from "react-icons/md";
2 |
3 | const FormButton = ({ size, remove, add }) => {
4 |
5 | return (
6 |
7 |
10 |
11 |
12 | {
13 | size > 0 &&
14 |
17 |
18 |
19 | }
20 |
21 | )
22 | }
23 |
24 | export default FormButton;
--------------------------------------------------------------------------------
/components/form/FormCP.jsx:
--------------------------------------------------------------------------------
1 | import React, { } from "react";
2 | import { BsFillArrowRightCircleFill, BsFillArrowLeftCircleFill } from "react-icons/bs"
3 |
4 | const FormCP = ({ formClose, setFormClose }) => {
5 | return (
6 | setFormClose(!formClose)}
10 | >
11 | {formClose ? : }
12 |
13 | )
14 | }
15 |
16 | export default FormCP;
17 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | colors: {
12 | background: "var(--background)",
13 | foreground: "var(--foreground)",
14 | },
15 | animation: {
16 | shimmer: "shimmer 2s linear infinite"
17 | },
18 | keyframes: {
19 | shimmer: {
20 | from: {
21 | backgroundPosition: "0 0"
22 | },
23 | to: {
24 | backgroundPosition: "-200% 0"
25 | }
26 | }
27 | }
28 | },
29 | },
30 | plugins: [],
31 | };
32 | export default config;
33 |
--------------------------------------------------------------------------------
/components/preview/ContactInfo.jsx:
--------------------------------------------------------------------------------
1 | import React, { } from "react";
2 |
3 | const ContactInfo = ({ mainclass, linkclass, teldata, emaildata, addressdata, telicon, emailicon, addressicon }) => {
4 | return (
5 |
22 | );
23 | }
24 |
25 | export default ContactInfo;
--------------------------------------------------------------------------------
/components/preview/Skills.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 |
4 | const Skills = ({ title, skills }) => {
5 | const { resumeData, setResumeData } = useContext(ResumeContext);
6 |
7 | const handleTitleChange = (e) => {
8 | const newSkills = [...resumeData.skills];
9 | newSkills.find((skillType) => skillType.title === title).title = e.target.innerText;
10 | setResumeData({ ...resumeData, skills: newSkills });
11 | };
12 |
13 | return (
14 | skills.length > 0 && (
15 | <>
16 |
17 | {title}
18 |
19 | {skills.join(", ")}
20 | >
21 | )
22 | );
23 | };
24 |
25 | export default Skills;
--------------------------------------------------------------------------------
/components/form/Summary.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 | import AISuggestionButton from '../ai/AISuggestionButton';
4 |
5 | const Summary = () => {
6 | const { resumeData, handleChange } = useContext(ResumeContext);
7 |
8 | return (
9 |
28 | );
29 | };
30 |
31 | export default Summary;
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "free-resume-maker",
3 | "version": "0.3.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@google/generative-ai": "^0.21.0",
13 | "@tsparticles/engine": "^3.8.1",
14 | "@tsparticles/react": "^3.0.0",
15 | "@tsparticles/slim": "^3.8.1",
16 | "@types/node": "18.11.18",
17 | "@types/react": "18.0.27",
18 | "@types/react-dom": "18.0.10",
19 | "clsx": "^2.1.1",
20 | "eslint": "8.33.0",
21 | "eslint-config-next": "13.1.6",
22 | "motion": "^12.4.3",
23 | "next": "14.2.15",
24 | "react": "^18",
25 | "react-beautiful-dnd": "^13.1.1",
26 | "react-dom": "^18",
27 | "react-icons": "^4.7.1",
28 | "react-simple-typewriter": "^5.0.1",
29 | "tailwind-merge": "^3.0.1"
30 | },
31 | "devDependencies": {
32 | "autoprefixer": "^10.4.13",
33 | "postcss": "^8",
34 | "tailwindcss": "^3.4.1",
35 | "typescript": "^5"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/components/form/Language.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 | import FormButton from "./FormButton";
4 |
5 | const Language = () => {
6 | const { resumeData, setResumeData } = useContext(ResumeContext);
7 | const skillType = "languages";
8 | const title = "Languages";
9 | const placeholder = "Language";
10 |
11 | const handleSkills = (e, index, skillType) => {
12 | const newSkills = [...resumeData[skillType]];
13 | newSkills[index] = e.target.value;
14 | setResumeData({ ...resumeData, [skillType]: newSkills });
15 | };
16 |
17 | const addSkill = () => {
18 | setResumeData({ ...resumeData, [skillType]: [...resumeData[skillType], ""] });
19 | };
20 |
21 | const removeSkill = (index) => {
22 | const newSkills = [...resumeData[skillType]];
23 | newSkills.splice(-1, 1);
24 | setResumeData({ ...resumeData, [skillType]: newSkills });
25 | };
26 |
27 | return (
28 |
29 |
{title}
30 | {resumeData[skillType].map((skill, index) => (
31 |
32 | handleSkills(e, index, skillType)}
39 | />
40 |
41 | ))}
42 |
43 |
44 | );
45 | };
46 |
47 | export default Language;
--------------------------------------------------------------------------------
/components/ai/AIAnalysis.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { analyzeResume } from '../../utils/gemini';
3 | import { FaRobot, FaSpinner } from 'react-icons/fa';
4 |
5 | const AIAnalysis = ({ resumeData }) => {
6 | const [analysis, setAnalysis] = useState('');
7 | const [loading, setLoading] = useState(false);
8 |
9 | const handleAnalyze = async () => {
10 | setLoading(true);
11 | try {
12 | const result = await analyzeResume(resumeData);
13 | setAnalysis(result);
14 | } catch (error) {
15 | console.error('Analysis error:', error);
16 | }
17 | setLoading(false);
18 | };
19 |
20 | return (
21 |
22 |
27 | {loading ? : }
28 |
29 |
30 | {analysis && (
31 |
33 |
AI Resume Analysis
34 |
{analysis}
35 |
setAnalysis('')}
37 | className="mt-4 bg-zinc-800 text-white px-4 py-2 rounded"
38 | >
39 | Close
40 |
41 |
42 | )}
43 |
44 | );
45 | };
46 |
47 | export default AIAnalysis;
--------------------------------------------------------------------------------
/pages/api/suggestions.js:
--------------------------------------------------------------------------------
1 | import { getSuggestions } from '../../utils/gemini';
2 |
3 | export default async function handler(req, res) {
4 | if (req.method !== 'POST') {
5 | return res.status(405).json({
6 | error: 'Method not allowed',
7 | message: 'Only POST requests are accepted'
8 | });
9 | }
10 |
11 | const { section, content } = req.body;
12 |
13 | if (!section || !content) {
14 | return res.status(400).json({
15 | error: 'VALIDATION_ERROR',
16 | message: 'Missing required fields: section and content'
17 | });
18 | }
19 |
20 | try {
21 | const suggestions = await getSuggestions(section, content);
22 | return res.status(200).json({ suggestions });
23 | } catch (error) {
24 | console.error('API Error:', error);
25 |
26 | // Handle specific error types
27 | if (error.message.startsWith('CONTENT_ERROR:')) {
28 | return res.status(400).json({
29 | error: 'CONTENT_ERROR',
30 | message: error.message.replace('CONTENT_ERROR: ', '')
31 | });
32 | }
33 |
34 | if (error.message.startsWith('QUOTA_ERROR:')) {
35 | return res.status(429).json({
36 | error: 'QUOTA_ERROR',
37 | message: error.message.replace('QUOTA_ERROR: ', '')
38 | });
39 | }
40 |
41 | if (error.message.startsWith('API_ERROR:')) {
42 | return res.status(500).json({
43 | error: 'API_ERROR',
44 | message: error.message.replace('API_ERROR: ', '')
45 | });
46 | }
47 |
48 | if (error.message.startsWith('RATE_ERROR:')) {
49 | return res.status(429).json({
50 | error: 'RATE_ERROR',
51 | message: error.message.replace('RATE_ERROR: ', '')
52 | });
53 | }
54 |
55 | // Generic error fallback
56 | return res.status(500).json({
57 | error: 'UNKNOWN_ERROR',
58 | message: 'An unexpected error occurred. Please try again.'
59 | });
60 | }
61 | }
--------------------------------------------------------------------------------
/components/ai/AISuggestionButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FaLightbulb, FaSpinner } from 'react-icons/fa';
3 | import { getSuggestions } from '../../utils/gemini';
4 |
5 | const AISuggestionButton = ({ section, content }) => {
6 | const [suggestions, setSuggestions] = useState('');
7 | const [loading, setLoading] = useState(false);
8 |
9 | const handleGetSuggestions = async () => {
10 | setLoading(true);
11 | try {
12 | const response = await fetch('/api/suggestions', {
13 | method: 'POST',
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | },
17 | body: JSON.stringify({
18 | section,
19 | content
20 | }),
21 | });
22 | const data = await response.json();
23 | setSuggestions(data.suggestions);
24 | } catch (error) {
25 | console.error('Suggestions error:', error);
26 | }
27 | setLoading(false);
28 | };
29 |
30 | return (
31 |
32 |
33 |
38 | {loading ? : }
39 |
40 |
41 |
42 | {suggestions && (
43 |
44 |
AI Suggestions:
45 |
{suggestions}
46 |
setSuggestions('')}
48 | className="mt-2 bg-zinc-800 text-white px-3 py-1 rounded hover:bg-zinc-700"
49 | >
50 | Dismiss
51 |
52 |
53 | )}
54 |
55 | );
56 | };
57 |
58 | export default AISuggestionButton;
--------------------------------------------------------------------------------
/components/form/PersonalInformation.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 | const PersonalInformation = ({}) => {
4 | const { resumeData, setResumeData, handleProfilePicture, handleChange } =
5 | useContext(ResumeContext);
6 |
7 | return (
8 |
63 | );
64 | };
65 |
66 | export default PersonalInformation;
67 |
--------------------------------------------------------------------------------
/components/form/certification.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 | import FormButton from "./FormButton";
4 |
5 | const Certification = () => {
6 | const { resumeData, setResumeData } = useContext(ResumeContext);
7 | const skillType = "certifications";
8 | const title = "Certifications";
9 |
10 | const handleCertification = (e, index) => {
11 | const newCertifications = [...resumeData[skillType]];
12 | newCertifications[index] = {
13 | ...newCertifications[index],
14 | [e.target.name]: e.target.value
15 | };
16 | setResumeData({ ...resumeData, [skillType]: newCertifications });
17 | };
18 |
19 | const addCertification = () => {
20 | setResumeData({
21 | ...resumeData,
22 | [skillType]: [
23 | ...resumeData[skillType],
24 | { name: "", issuer: "" }
25 | ]
26 | });
27 | };
28 |
29 | const removeCertification = () => {
30 | const newCertifications = [...resumeData[skillType]];
31 | newCertifications.pop();
32 | setResumeData({ ...resumeData, [skillType]: newCertifications });
33 | };
34 |
35 | return (
36 |
64 | );
65 | };
66 |
67 | export default Certification;
--------------------------------------------------------------------------------
/components/form/SocialMedia.jsx:
--------------------------------------------------------------------------------
1 | import FormButton from "./FormButton";
2 | import React, { useContext } from "react";
3 | import { ResumeContext } from "../../pages/builder";
4 |
5 | const SocialMedia = () => {
6 | const { resumeData, setResumeData } = useContext(ResumeContext);
7 |
8 | // social media
9 | const handleSocialMedia = (e, index) => {
10 | const newSocialMedia = [...resumeData.socialMedia];
11 | newSocialMedia[index][e.target.name] = e.target.value.replace(
12 | "https://",
13 | ""
14 | );
15 | setResumeData({ ...resumeData, socialMedia: newSocialMedia });
16 | };
17 |
18 | const addSocialMedia = () => {
19 | setResumeData({
20 | ...resumeData,
21 | socialMedia: [...resumeData.socialMedia, { socialMedia: "", link: "" }],
22 | });
23 | };
24 |
25 | const removeSocialMedia = (index) => {
26 | const newSocialMedia = [...resumeData.socialMedia];
27 | newSocialMedia[index] = newSocialMedia[newSocialMedia.length - 1];
28 | newSocialMedia.pop();
29 | setResumeData({ ...resumeData, socialMedia: newSocialMedia });
30 | };
31 |
32 | return (
33 |
61 | );
62 | };
63 |
64 | export default SocialMedia;
65 |
--------------------------------------------------------------------------------
/components/form/LoadUnload.jsx:
--------------------------------------------------------------------------------
1 | import { FaCloudUploadAlt, FaCloudDownloadAlt } from "react-icons/fa";
2 | import React, { useContext } from "react";
3 | import { ResumeContext } from "../../pages/builder";
4 |
5 | const LoadUnload = () => {
6 | const { resumeData, setResumeData } = useContext(ResumeContext);
7 |
8 | // load backup resume data
9 | const handleLoad = (event) => {
10 | const file = event.target.files[0];
11 | const reader = new FileReader();
12 | reader.onload = (event) => {
13 | const resumeData = JSON.parse(event.target.result);
14 | setResumeData(resumeData);
15 | };
16 | reader.readAsText(file);
17 | };
18 |
19 | // download resume data
20 | const handleDownload = (data, filename, event) => {
21 | event.preventDefault();
22 | const jsonData = JSON.stringify(data);
23 | const blob = new Blob([jsonData], { type: "application/json" });
24 | const link = document.createElement("a");
25 | link.href = URL.createObjectURL(blob);
26 | link.download = filename;
27 | link.click();
28 | };
29 |
30 | return (
31 |
32 |
33 |
Load Data
34 |
35 |
36 |
43 |
44 |
45 |
46 |
Save Data
47 |
51 | handleDownload(
52 | resumeData,
53 | resumeData.name + " by ATSResume.json",
54 | event
55 | )
56 | }
57 | >
58 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default LoadUnload;
66 |
--------------------------------------------------------------------------------
/components/form/Skill.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ResumeContext } from "../../pages/builder";
3 | import FormButton from "./FormButton";
4 |
5 | const Skill = ({ title }) => {
6 | const { resumeData, setResumeData } = useContext(ResumeContext);
7 |
8 | // skills
9 | const handleSkill = (e, index, title) => {
10 | const newSkills = [
11 | ...resumeData.skills.find((skillType) => skillType.title === title)
12 | .skills,
13 | ];
14 | newSkills[index] = e.target.value;
15 | setResumeData((prevData) => ({
16 | ...prevData,
17 | skills: prevData.skills.map((skill) =>
18 | skill.title === title ? { ...skill, skills: newSkills } : skill
19 | ),
20 | }));
21 | };
22 |
23 | const addSkill = (title) => {
24 | setResumeData((prevData) => {
25 | const skillType = prevData.skills.find(
26 | (skillType) => skillType.title === title
27 | );
28 | const newSkills = [...skillType.skills, ""];
29 | const updatedSkills = prevData.skills.map((skill) =>
30 | skill.title === title ? { ...skill, skills: newSkills } : skill
31 | );
32 | return {
33 | ...prevData,
34 | skills: updatedSkills,
35 | };
36 | });
37 | };
38 |
39 | const removeSkill = (title, index) => {
40 | setResumeData((prevData) => {
41 | const skillType = prevData.skills.find(
42 | (skillType) => skillType.title === title
43 | );
44 | const newSkills = [...skillType.skills];
45 | newSkills.pop();
46 | const updatedSkills = prevData.skills.map((skill) =>
47 | skill.title === title ? { ...skill, skills: newSkills } : skill
48 | );
49 | return {
50 | ...prevData,
51 | skills: updatedSkills,
52 | };
53 | });
54 | };
55 |
56 | const skillType = resumeData.skills.find(
57 | (skillType) => skillType.title === title
58 | );
59 |
60 | return (
61 |
62 |
{title}
63 | {skillType.skills.map((skill, index) => (
64 |
65 | handleSkill(e, index, title)}
72 | />
73 |
74 | ))}
75 |
addSkill(title)}
78 | remove={() => removeSkill(title)}
79 | />
80 |
81 | );
82 | };
83 |
84 | export default Skill;
85 |
--------------------------------------------------------------------------------
/components/form/Education.jsx:
--------------------------------------------------------------------------------
1 | import FormButton from "./FormButton";
2 | import React, { useContext } from "react";
3 | import { ResumeContext } from "../../pages/builder";
4 |
5 | const Education = () => {
6 | const { resumeData, setResumeData} = useContext(ResumeContext);
7 |
8 | const handleEducation = (e, index) => {
9 | const newEducation = [...resumeData.education];
10 | newEducation[index][e.target.name] = e.target.value;
11 | setResumeData({ ...resumeData, education: newEducation });
12 | };
13 |
14 | const addEducation = () => {
15 | setResumeData({
16 | ...resumeData,
17 | education: [
18 | ...resumeData.education,
19 | { school: "", degree: "", startYear: "", endYear: "" },
20 | ],
21 | });
22 | };
23 |
24 | const removeEducation = (index) => {
25 | const newEducation = [...resumeData.education];
26 | newEducation[index] = newEducation[newEducation.length - 1];
27 | newEducation.pop();
28 | setResumeData({ ...resumeData, education: newEducation });
29 | };
30 |
31 | return (
32 |
33 |
Education
34 | {resumeData.education.map((education, index) => (
35 |
67 | ))}
68 |
69 |
70 | )
71 | }
72 |
73 | export default Education;
--------------------------------------------------------------------------------
/components/meta/Meta.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 |
3 | export default function Meta({ title, keywords, description }) {
4 | const homepage = "https://free-next-resume-maker.vercel.app";
5 | const logo = "/assets/logo.png";
6 | const fevicon = "/assets/logo.png";
7 | function isiteJsonLd() {
8 | return {
9 | __html: `{
10 | "@context": "https://schema.org",
11 | "@type": "Organization",
12 | "url": ${homepage},
13 | "logo": ${logo},
14 | "contactPoint": {
15 | "@type": "ContactPoint",
16 | "telephone": "+91 1234567890",
17 | "contactType": "customer service"
18 | },
19 | "image": ${logo},
20 | "description": ${description},
21 | "founder": "HOT HEAD",
22 | "foundingDate": "2024",
23 | "foundingLocation": "IN",
24 | "email": "xyz@gmail.com",
25 | "telephone": "+91 1234567890",
26 | "areaServed": "IN",
27 | "keywords": ${keywords},
28 | "mainEntityOfPage": ${homepage},
29 | "knowsAbout": ${keywords},
30 | "knowsLanguage": "English",
31 | "memberOf": "Oensource",
32 | "owns": "HOT HEAD",
33 | "publishingPrinciples": ${homepage},
34 | "slogan": "free resume maker for humanity"
35 | }`
36 | }
37 | }
38 |
39 |
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
47 | {title}
48 |
49 |
50 | {/* Open Graph */}
51 |
52 |
53 |
54 |
57 |
58 | {/* Twitter */}
59 |
60 |
61 |
62 |
65 |
66 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 |
7 | body {
8 | @apply font-[sans-serif];
9 | }
10 |
11 | ::-webkit-scrollbar {
12 | @apply w-2.5 h-2.5;
13 | }
14 |
15 | ::-webkit-scrollbar-track {
16 | @apply bg-gray-200;
17 | }
18 |
19 | ::-webkit-scrollbar-thumb {
20 | @apply bg-zinc-700;
21 | }
22 |
23 | ::-webkit-scrollbar-thumb:hover {
24 | @apply bg-zinc-600;
25 | }
26 |
27 | textarea, input:focus {
28 | @apply focus:outline-none;
29 | }
30 |
31 | .ul-padding{
32 | padding-inline-start: 0.9rem;
33 | }
34 |
35 | .ul-padding-none{
36 | padding-inline-start: 0;
37 | }
38 | }
39 |
40 | @layer components{
41 | .pi{
42 | @apply p-2 mb-2 text-white bg-zinc-800/70 backdrop-blur-sm rounded text-[0.9rem] border border-white/10;
43 | }
44 |
45 | .profileInput{
46 | @apply pi file:border-0 file:bg-zinc-800 file:text-white file:rounded-sm;
47 | }
48 |
49 | .flex-col-gap-2{
50 | @apply flex flex-col gap-2;
51 | }
52 |
53 | .flex-wrap-gap-2{
54 | @apply flex flex-wrap gap-2;
55 | }
56 |
57 | .other-input{
58 | @apply p-2 mb-2 text-white bg-zinc-800/70 backdrop-blur-sm rounded focus:placeholder-transparent border border-white/10;
59 | }
60 |
61 | .input-title{
62 | @apply text-[1rem] text-white font-semibold;
63 | }
64 |
65 | .f-col{
66 | @apply flex flex-col;
67 | }
68 |
69 | .grid-4{
70 | @apply grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-4;
71 | }
72 |
73 | .editable{
74 | @apply hover:bg-gray-100 hover:cursor-text outline-none;
75 | }
76 |
77 | }
78 |
79 | .name{
80 | font-size: 20px;
81 | font-weight: 700;
82 | }
83 |
84 | .profession{
85 | font-size: 16px;
86 | font-weight: 500;
87 | }
88 |
89 | .contact{
90 | font-size: 14px;
91 | font-weight: 400;
92 | }
93 |
94 | .social-media{
95 | font-size: 12px;
96 | font-weight: 400;
97 | }
98 |
99 | .section-title{
100 | font-size: 16px;
101 | font-weight: 700;
102 | }
103 |
104 | .content{
105 | font-size: 14px;
106 | font-weight: 400;
107 | }
108 |
109 | .sub-content{
110 | font-size: 12px;
111 | font-weight: 400;
112 | }
113 |
114 | .i-bold{
115 | font-weight: 700 !important;
116 | }
117 |
118 | @media print {
119 | .exclude-print {
120 | display: none !important;
121 | }
122 |
123 | .rm-padding-print {
124 | padding: 0;
125 | }
126 |
127 | @page {
128 | size: A4;
129 | margin: 10mm;
130 | }
131 |
132 | .preview {
133 | overflow-y: visible !important;
134 | }
135 |
136 | .preview::after {
137 | content: "Made using : free-next-resume-maker.vercel.app ";
138 | position: fixed;
139 | bottom: 0;
140 | right: 0;
141 | opacity: 0.2;
142 | font-size: small;
143 | text-align: center;
144 | z-index: -1;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/utils/gemini.js:
--------------------------------------------------------------------------------
1 | const { GoogleGenerativeAI } = require("@google/generative-ai");
2 |
3 | // Initialize with API version v1
4 | const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
5 |
6 | export async function analyzeResume(resumeData) {
7 | if (!process.env.GEMINI_API_KEY) {
8 | throw new Error("Missing Gemini API key");
9 | }
10 |
11 | const model = genAI.getGenerativeModel({ model: "gemini-pro" });
12 |
13 | const prompt = `Analyze this resume data and provide feedback on:
14 | 1. Overall strength and weaknesses
15 | 2. ATS optimization suggestions
16 | 3. Content improvement recommendations
17 |
18 | Resume Data:
19 | ${JSON.stringify(resumeData, null, 2)}`;
20 |
21 | try {
22 | const result = await model.generateContent(prompt);
23 | const response = await result.response;
24 | return response.text();
25 | } catch (error) {
26 | console.error("Error analyzing resume:", error);
27 | return "Error analyzing resume. Please try again.";
28 | }
29 | }
30 |
31 | export async function getSuggestions(section, currentContent) {
32 | // Check for API key first
33 | if (!process.env.GEMINI_API_KEY) {
34 | throw new Error("API_ERROR: Missing or invalid Gemini API key");
35 | }
36 |
37 | // Validate content
38 | if (!currentContent || currentContent.trim() === '') {
39 | throw new Error("CONTENT_ERROR: Please write something first to get suggestions");
40 | }
41 |
42 | // Check for minimum content length
43 | if (currentContent.trim().length < 10) {
44 | throw new Error("CONTENT_ERROR: Please write at least a few words to get meaningful suggestions");
45 | }
46 |
47 | try {
48 | const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
49 |
50 | const prompt = `You are a friendly resume writing assistant. Look at this ${section} content and suggest 2 slightly improved versions.
51 | Keep the same meaning and information, but make it flow better. Use natural, conversational language - no complex jargon.
52 | Make it sound like a real person wrote it.
53 |
54 | Important guidelines:
55 | - Keep the same key points and achievements
56 | - Use simple, clear language
57 | - Make it sound natural and conversational
58 | - Keep a similar length
59 | - Don't add new information
60 |
61 | Current content:
62 | ${currentContent}
63 |
64 | Format your response as:
65 | Option 1:
66 | [first natural improvement]
67 |
68 | Option 2:
69 | [second natural improvement]`;
70 |
71 | const result = await model.generateContent(prompt);
72 | return result.response.text();
73 | } catch (error) {
74 | console.error("Gemini API Error:", error);
75 |
76 | // Handle specific API errors
77 | if (error.message?.includes('quota')) {
78 | throw new Error("QUOTA_ERROR: API quota exceeded. Please try again later");
79 | }
80 | if (error.message?.includes('invalid')) {
81 | throw new Error("API_ERROR: Invalid API key");
82 | }
83 | if (error.message?.includes('rate')) {
84 | throw new Error("RATE_ERROR: Too many requests. Please wait a moment");
85 | }
86 |
87 | // Generic error fallback
88 | throw new Error(`API_ERROR: ${error.message || 'Failed to generate suggestions'}`);
89 | }
90 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Free Resume Maker
2 |
3 | ### A modern, AI-powered, ATS-optimized resume builder featuring professionally designed templates and smart content suggestions.
4 |
5 | ## GitAds Sponsored
6 | [](https://gitads.dev/v1/ad-track?source=hothead01th/free-resume-maker@github)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ## ✨ Key Features
18 |
19 | - **AI-Powered Content Suggestions**: Smart content optimization using Google's Gemini API
20 | - **Multiple Professional Templates**: Choose from carefully crafted, industry-standard resume layouts
21 | - **Smart Content Enhancement**: Get real-time suggestions to improve your resume content
22 | - **ATS-Friendly**: Engineered to pass Applicant Tracking Systems with flying colors
23 | - **Real-time Preview**: Instant visual feedback as you build your resume
24 | - **Drag & Drop Sections**: Easily customize section order to highlight your strengths
25 | - **Project Links**: Showcase your work with integrated project links and icons
26 | - **Modern UI/UX**: Clean, intuitive interface with a seamless user experience
27 | - **Export to PDF**: One-click PDF download for your polished resume
28 | - **Responsive Design**: Perfect resume building experience across all devices
29 | - **Zero Cost**: Completely free and open-source
30 |
31 | ## 🛠️ Built With
32 |
33 | - Next.js - React Framework
34 | - Tailwind CSS - Styling
35 | - Google Gemini API - AI Content Suggestions
36 | - React Beautiful DnD - Drag and Drop functionality
37 | - React Icons - Professional icons
38 |
39 | ## 📦 Installation
40 |
41 | 1. Clone the repository:
42 | ```bash
43 | git clone https://github.com/HOTHEAD01TH/free-resume-maker.git
44 | ```
45 |
46 | 2. Navigate to the project directory:
47 | ```bash
48 | cd free-resume-maker
49 | ```
50 |
51 | 3. Install dependencies:
52 | ```bash
53 | npm install
54 | ```
55 |
56 | 4. Set up environment variables:
57 | ```bash
58 | GEMINI_API_KEY=your_api_key_here
59 | ```
60 |
61 | 5. Start the development server:
62 | ```bash
63 | npm run dev
64 | ```
65 |
66 | 6. Open your browser and navigate to `http://localhost:3000` to see the application in action.
67 |
68 | ## 📝 Usage Tips
69 |
70 | 1. **AI Suggestions**: Click the lightbulb icon to get smart content improvements
71 | 2. **Profile Section**: Start with your personal information and professional summary
72 | 3. **Experience & Projects**: Use action verbs and quantify achievements
73 | 4. **Skills & Certifications**: Include relevant technical and soft skills
74 | 5. **Project Links**: Add GitHub/live links to showcase your work
75 | 6. **Customization**: Drag and drop sections to create the perfect layout
76 | 7. **Download**: Before downloading, disable header/footer in print settings for optimal PDF output
77 |
78 | ## 🤝 Contributing
79 |
80 | Contributions are welcome! Please feel free to submit a Pull Request.
81 |
82 | ## 📄 License
83 |
84 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
85 |
86 | ## 🙏 Acknowledgments
87 |
88 | - Special thanks to the open-source community and [Github Projects](https://x.com/githubprojects)
89 | - Inspired by modern recruitment needs and ATS requirements
90 |
91 | ## ⚠️ Important Note
92 |
93 | When downloading your resume as PDF, ensure you disable header and footer in your browser's print settings for the best results.
94 |
95 | ## Star History
96 |
97 | [](https://star-history.com/#HOTHEAD01TH/free-resume-maker&Date)
98 |
99 | ---
100 |
101 | Made with ❤️ by Zaid for job seekers worldwide!
102 |
--------------------------------------------------------------------------------
/components/form/Projects.jsx:
--------------------------------------------------------------------------------
1 | import FormButton from "./FormButton";
2 | import React, { useContext } from "react";
3 | import { ResumeContext } from "../../pages/builder";
4 | import AISuggestionButton from '../ai/AISuggestionButton';
5 |
6 | const Projects = () => {
7 | const { resumeData, setResumeData } = useContext(ResumeContext);
8 |
9 | const handleProjects = (e, index) => {
10 | const newProjects = [...resumeData.projects];
11 | newProjects[index][e.target.name] = e.target.value;
12 | setResumeData({ ...resumeData, projects: newProjects });
13 | };
14 |
15 | const addProjects = () => {
16 | setResumeData({
17 | ...resumeData,
18 | projects: [
19 | ...resumeData.projects,
20 | {
21 | title: "",
22 | link: "",
23 | description: "",
24 | keyAchievements: "",
25 | startYear: "",
26 | endYear: "",
27 | },
28 | ],
29 | });
30 | };
31 |
32 | const removeProjects = (index) => {
33 | const newProjects = [...resumeData.projects];
34 | newProjects[index] = newProjects[newProjects.length - 1];
35 | newProjects.pop();
36 | setResumeData({ ...resumeData, projects: newProjects });
37 | };
38 |
39 | return (
40 |
41 |
Projects
42 | {resumeData.projects.map((project, index) => (
43 |
44 |
handleProjects(e, index)}
51 | />
52 |
handleProjects(e, index)}
59 | />
60 |
61 |
Description
62 |
66 |
67 |
110 | ))}
111 |
116 |
117 | );
118 | };
119 |
120 | export default Projects;
121 |
--------------------------------------------------------------------------------
/components/form/WorkExperience.jsx:
--------------------------------------------------------------------------------
1 | import FormButton from "./FormButton";
2 | import React, { useContext } from "react";
3 | import { ResumeContext } from "../../pages/builder";
4 | import AISuggestionButton from '../ai/AISuggestionButton';
5 |
6 | const WorkExperience = () => {
7 | const {
8 | resumeData,
9 | setResumeData,
10 | } = useContext(ResumeContext);
11 |
12 | const handleWorkExperience = (e, index) => {
13 | const newworkExperience = [...resumeData.workExperience];
14 | newworkExperience[index][e.target.name] = e.target.value;
15 | setResumeData({ ...resumeData, workExperience: newworkExperience });
16 | };
17 |
18 | const addWorkExperience = () => {
19 | setResumeData({
20 | ...resumeData,
21 | workExperience: [
22 | ...resumeData.workExperience,
23 | {
24 | company: "",
25 | position: "",
26 | description: "",
27 | keyAchievements: "",
28 | startYear: "",
29 | endYear: "",
30 | },
31 | ],
32 | });
33 | };
34 |
35 | const removeWorkExperience = (index) => {
36 | const newworkExperience = [...resumeData.workExperience];
37 | newworkExperience[index] = newworkExperience[newworkExperience.length - 1];
38 | newworkExperience.pop();
39 | setResumeData({ ...resumeData, workExperience: newworkExperience });
40 | };
41 |
42 | return (
43 |
44 |
Work Experience
45 | {resumeData.workExperience.map((workExperience, index) => (
46 |
113 | ))}
114 |
119 |
120 | );
121 | };
122 |
123 | export default WorkExperience;
124 |
--------------------------------------------------------------------------------
/pages/templates.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import { motion } from "framer-motion";
4 | import { SparklesCore } from "../components/ui/sparkles";
5 |
6 | const templates = [
7 | {
8 | id: 1,
9 | name: "Google Style",
10 | image: "/assets/resume.jpg",
11 | description: "Clean, metrics-driven format preferred by Google recruiters",
12 | style: "modern",
13 | features: [
14 | "Quantifiable achievements",
15 | "Clear section hierarchy",
16 | "Bullet-point focused"
17 | ]
18 | },
19 | {
20 | id: 2,
21 | name: "Amazon Leadership",
22 | image: "/assets/resume.jpg",
23 | description: "Emphasizes leadership principles and results",
24 | style: "leadership",
25 | features: [
26 | "STAR format bullets",
27 | "Leadership principles aligned",
28 | "Metrics highlighted"
29 | ]
30 | },
31 | {
32 | id: 3,
33 | name: "Meta Technical",
34 | image: "/assets/resume.jpg",
35 | description: "Technical skills-focused layout for engineering roles",
36 | style: "technical",
37 | features: [
38 | "Technical skills matrix",
39 | "Project highlights",
40 | "System design experience"
41 | ]
42 | },
43 | {
44 | id: 4,
45 | name: "Apple Design",
46 | image: "/assets/resume.jpg",
47 | description: "Clean, minimalist design with strong typography",
48 | style: "minimal",
49 | features: [
50 | "Typography focused",
51 | "Whitespace optimized",
52 | "Visual hierarchy"
53 | ]
54 | },
55 | {
56 | id: 5,
57 | name: "Minimal Classic",
58 | image: "/assets/resume.jpg",
59 | description: "Simple, elegant, and professional layout",
60 | style: "minimal",
61 | features: [
62 | "Clean typography",
63 | "Perfect spacing",
64 | "Traditional structure"
65 | ]
66 | }
67 | ];
68 |
69 | export default function TemplatesPage() {
70 | return (
71 |
72 | {/* Sparkles Background */}
73 |
74 |
83 |
84 |
85 |
86 |
87 | Choose Your Template
88 |
89 |
90 |
91 | {templates.map((template) => (
92 |
93 | ))}
94 |
95 |
96 |
97 | );
98 | }
99 |
100 | function TemplateCard({ template }) {
101 | return (
102 |
106 |
107 |
112 |
113 |
117 | Use Template
118 |
119 |
120 |
121 |
122 |
{template.name}
123 |
{template.description}
124 |
125 |
126 | );
127 | }
--------------------------------------------------------------------------------
/pages/builder.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext, useContext } from "react";
2 | import Language from "../components/form/Language";
3 | import Meta from "../components/meta/Meta";
4 | import FormCP from "../components/form/FormCP";
5 | import LoadUnload from "../components/form/LoadUnload";
6 | import Preview from "../components/preview/Preview";
7 | import DefaultResumeData from "../components/utility/DefaultResumeData";
8 | import SocialMedia from "../components/form/SocialMedia";
9 | import WorkExperience from "../components/form/WorkExperience";
10 | import Skill from "../components/form/Skill";
11 | import PersonalInformation from "../components/form/PersonalInformation";
12 | import Summary from "../components/form/Summary";
13 | import Projects from "../components/form/Projects";
14 | import Education from "../components/form/Education";
15 | import dynamic from "next/dynamic";
16 | import Certification from "../components/form/certification";
17 | import { SparklesCore } from "../components/ui/sparkles";
18 |
19 | const ResumeContext = createContext(DefaultResumeData);
20 |
21 | // server side rendering false
22 | const Print = dynamic(() => import("../components/utility/WinPrint"), {
23 | ssr: false,
24 | });
25 |
26 | export default function Builder(props) {
27 | // resume data
28 | const [resumeData, setResumeData] = useState(DefaultResumeData);
29 |
30 | // form hide/show
31 | const [formClose, setFormClose] = useState(false);
32 |
33 | // profile picture
34 | const handleProfilePicture = (e) => {
35 | const file = e.target.files[0];
36 |
37 | if (file instanceof Blob) {
38 | const reader = new FileReader();
39 | reader.onload = (event) => {
40 | setResumeData({ ...resumeData, profilePicture: event.target.result });
41 | };
42 | reader.readAsDataURL(file);
43 | } else {
44 | console.error("Invalid file type");
45 | }
46 | };
47 |
48 | const handleChange = (e) => {
49 | setResumeData({ ...resumeData, [e.target.name]: e.target.value });
50 | console.log(resumeData);
51 | };
52 |
53 | return (
54 | <>
55 |
63 |
68 |
69 | {!formClose && (
70 |
71 |
72 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | {resumeData.skills.map((skill, index) => (
91 |
95 | ))}
96 |
97 |
98 |
99 |
100 | )}
101 |
102 |
103 |
104 |
105 |
106 | >
107 | );
108 | }
109 | export { ResumeContext };
110 |
--------------------------------------------------------------------------------
/components/utility/DefaultResumeData.jsx:
--------------------------------------------------------------------------------
1 | const DefaultResumeData = {
2 | name: "ZAID ADIL",
3 | position: "Full Stack Developer",
4 | contactInformation: "+91 123456789",
5 | email: "hothead01th@gmail.com",
6 | address: "Kashmir, India",
7 | profilePicture: "",
8 | socialMedia: [
9 | {
10 | socialMedia: "Github",
11 | link: "github.com//HOTHEAD01TH",
12 | },
13 | {
14 | socialMedia: "LinkedIn",
15 | link: "linkedin.com/in/hothead01th",
16 | },
17 | {
18 | socialMedia: "Website",
19 | link: "hothead.vercel.app",
20 | },
21 | ],
22 | summary: "A Full Stack Developer with 3 years of experience in web development, specializing in building dynamic and responsive web applications. Passionate about Web3 and DevOps, with a keen interest in decentralized technologies and automation in the development process. Skilled in both front-end and back-end development, with a focus on delivering efficient and scalable solutions.",
23 | education: [
24 | {
25 | "school": "Kashmir University",
26 | "degree": "Bachelors in Computer Application",
27 | "startYear": "2022-10-01",
28 | "endYear": "2025-07-01"
29 | },
30 | ],
31 | workExperience: [
32 | {
33 | "company": "Techflare",
34 | "position": "Full Stack Developer",
35 | "description": "Worked as a full stack developer on multiple client projects, focusing on building scalable web applications using modern technologies. Collaborated with cross-functional teams to deliver high-quality solutions.",
36 | "keyAchievements": "Successfully delivered 5+ client projects with 100% satisfaction rate\nReduced application load time by 40% through optimization techniques",
37 | "startYear": "2023-10-01",
38 | "endYear": "2024-03-01"
39 | },
40 | {
41 | "company": "Techbug",
42 | "position": "Frontend Developer Intern",
43 | "description": "Contributed to the development of user interfaces for various web applications. Worked closely with senior developers to implement responsive designs and improve user experience.",
44 | "keyAchievements": "Developed responsive web interfaces using React and Tailwind CSS\nCollaborated with the design team to implement pixel-perfect UI components",
45 | "startYear": "2023-06-01",
46 | "endYear": "2023-09-01"
47 | }
48 | ],
49 | projects: [
50 |
51 | {
52 | "name": "AI Resume Maker",
53 | "description": "Developed an intelligent resume builder using Next.js with AI-powered content suggestions, multiple templates, and real-time preview functionality. Integrated with Google's Gemini API for smart content optimization.",
54 | "keyAchievements": "Implemented drag-and-drop functionality for section reordering\nIntegrated AI-powered content suggestions using Gemini API",
55 | "startYear": "2024-02-20",
56 | "endYear": "2024-03-15",
57 | "link": "https://github.com/HOTHEAD01TH/free-resume-maker"
58 | },
59 | {
60 | "name": "Paytm",
61 | "description": "Created a monorepo-based banking application using Express and AWS services. Implemented microservices architecture with features like transaction management, account services, and real-time notifications.",
62 | "keyAchievements": "Designed and implemented microservices architecture using Express\nImplemented real-time transaction notifications using AWS SNS",
63 | "startYear": "2024-03-20",
64 | "endYear": "2024-04-15",
65 | "link": "https://github.com/HOTHEAD01TH/paytm-advanced"
66 | }
67 | ],
68 | skills: [
69 | {
70 | title: "Technical Skills",
71 | skills: [
72 | "React", "Next.js", "TypeScript", "Tailwind CSS", "MongoDB", "PostgreSQL",
73 | "Express", "Python", "Java", "Docker", "AWS", "Kubernetes", "Jenkins",
74 | "Firebase", "Redis", "Node.js", "Git", "REST API"
75 | ]
76 | },
77 | {
78 | title: "Soft Skills",
79 | skills: [
80 | "Collaboration", "Problem-solving", "Communication", "Time management", "Result-oriented"
81 | ]
82 | },
83 | {
84 | title: "Additional Skills",
85 | skills: [
86 | "CI/CD", "Microservices", "System Design", "Cloud Architecture"
87 | ]
88 | }
89 | ],
90 | languages: [
91 | "English",
92 | "Hindi",
93 | "Urdu",
94 | "Kashmiri",
95 | ],
96 | certifications: [
97 | {
98 | name: "Java and Object-Oriented Programming",
99 | issuer: "University of Pennsylvania"
100 | },
101 | {
102 | name: "Introduction to Cloud Computing",
103 | issuer: "IBM"
104 | },
105 | {
106 | name: "Full Stack Development",
107 | issuer: "john hopkins university"
108 | },
109 | {
110 | name: "AI Primer Course",
111 | issuer: "emly labs"
112 | }
113 | ],
114 | };
115 |
116 | export default DefaultResumeData;
117 |
--------------------------------------------------------------------------------
/components/hero/Hero.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { SparklesCore } from "../ui/sparkles";
4 | import Link from "next/link";
5 | import { Typewriter } from 'react-simple-typewriter';
6 | import { Space_Grotesk } from 'next/font/google';
7 |
8 | const spaceGrotesk = Space_Grotesk({ subsets: ['latin'] });
9 |
10 | export default function Hero() {
11 | return (
12 |
13 | {/* GitHub Star Button */}
14 |
20 |
27 |
28 |
29 | Star on GitHub
30 |
31 |
32 |
33 |
42 |
43 |
44 |
45 | Free Resume Maker
46 |
47 |
48 |
49 |
58 |
59 |
60 | {/* Beam and additional sparkles container */}
61 |
62 | {/* Beam gradients */}
63 |
64 |
65 |
66 |
67 |
68 | {/* Additional sparkles under text */}
69 |
77 |
78 | {/* Radial gradient mask */}
79 |
80 |
81 |
82 |
86 | Create Resume
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/components/preview/TemplateTwo.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ContactInfo from "./ContactInfo";
3 | import Link from "next/link";
4 | import { FaExternalLinkAlt, FaGithub, FaLinkedin, FaTwitter, FaFacebook, FaInstagram, FaYoutube } from "react-icons/fa";
5 | import { CgWebsite } from "react-icons/cg";
6 | import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
7 | import Certification from "./Certification";
8 |
9 | const TemplateTwo = ({
10 | namedata,
11 | positiondata,
12 | contactdata,
13 | emaildata,
14 | addressdata,
15 | telicon,
16 | emailicon,
17 | addressicon,
18 | summarydata,
19 | educationdata,
20 | projectsdata,
21 | workExperiencedata,
22 | skillsdata,
23 | languagesdata,
24 | certificationsdata,
25 | sectionOrder,
26 | onDragEnd,
27 | resumeData,
28 | setResumeData
29 | }) => {
30 |
31 | const sections = [
32 | { id: "summary", title: "Summary", content: summarydata },
33 | { id: "education", title: "Education", content: educationdata },
34 | { id: "projects", title: "Projects", content: projectsdata },
35 | { id: "experience", title: "Work Experience", content: workExperiencedata },
36 | { id: "skills", title: "Technical Skills", content: skillsdata },
37 | { id: "softskills", title: "Soft Skills", content: skillsdata.find(skill => skill.title === "Soft Skills")?.skills || [] },
38 | { id: "languages", title: "Languages", content: languagesdata },
39 | { id: "certifications", title: "Certifications", content: certificationsdata }
40 | ];
41 |
42 | const orderedSections = sectionOrder
43 | .map(id => sections.find(section => section.id === id))
44 | .filter(section => section !== undefined);
45 |
46 | const renderSection = (section) => {
47 | switch(section.id) {
48 | case "certifications":
49 | return (
50 |
51 |
Certifications
52 |
53 | {certificationsdata && certificationsdata.map((cert, i) => (
54 |
55 | {cert.name}
56 | {cert.issuer && (
57 | - {cert.issuer}
58 | )}
59 |
60 | ))}
61 |
62 |
63 | );
64 | case "summary":
65 | return (
66 |
67 |
Summary
68 |
{summarydata}
69 |
70 | );
71 | case "education":
72 | return (
73 |
74 |
Education
75 | {educationdata.map((edu, idx) => (
76 |
77 |
{edu.school}
78 |
{edu.degree}
79 |
80 | {new Date(edu.startYear).getFullYear()} - {new Date(edu.endYear).getFullYear()}
81 |
82 |
83 | ))}
84 |
85 | );
86 | case "projects":
87 | return (
88 |
89 |
Projects
90 |
91 | {(provided) => (
92 |
93 | {projectsdata.map((project, idx) => (
94 |
95 | {(provided, snapshot) => (
96 |
102 |
103 |
104 |
{project.name}
105 | {project.link && (
106 |
113 |
114 |
115 | )}
116 |
117 |
118 | {new Date(project.startYear).getFullYear()} - {new Date(project.endYear).getFullYear()}
119 |
120 |
121 |
{project.description}
122 | {project.keyAchievements && (
123 |
124 | {project.keyAchievements.split('\n').map((achievement, i) => (
125 | {achievement}
126 | ))}
127 |
128 | )}
129 |
130 | )}
131 |
132 | ))}
133 | {provided.placeholder}
134 |
135 | )}
136 |
137 |
138 | );
139 | case "experience":
140 | return (
141 |
142 |
Work Experience
143 |
144 | {(provided) => (
145 |
146 | {workExperiencedata.map((work, idx) => (
147 |
148 | {(provided, snapshot) => (
149 |
155 |
156 |
157 | {work.company}
158 | -
159 | {work.position}
160 |
161 |
162 | {new Date(work.startYear).getFullYear()} - {new Date(work.endYear).getFullYear()}
163 |
164 |
165 |
{work.description}
166 |
{work.keyAchievements}
167 |
168 | )}
169 |
170 | ))}
171 | {provided.placeholder}
172 |
173 | )}
174 |
175 |
176 | );
177 | case "skills":
178 | return (
179 |
180 |
Technical Skills
181 |
182 | {skillsdata.find(skill => skill.title === "Technical Skills")?.skills.join(", ")}
183 |
184 |
185 | );
186 | case "softskills":
187 | return (
188 |
189 |
Soft Skills
190 |
191 | {section.content.join(", ")}
192 |
193 |
194 | );
195 | case "languages":
196 | return (
197 |
198 |
Languages
199 |
200 | {section.content.join(", ")}
201 |
202 |
203 | );
204 | default:
205 | return null;
206 | }
207 | };
208 |
209 | const icons = [
210 | { name: "github", icon: },
211 | { name: "linkedin", icon: },
212 | { name: "twitter", icon: },
213 | { name: "facebook", icon: },
214 | { name: "instagram", icon: },
215 | { name: "youtube", icon: },
216 | { name: "website", icon: },
217 | ];
218 |
219 | // Function to extract username from URL
220 | const getUsername = (url) => {
221 | return url.split('/').pop();
222 | };
223 |
224 | if (certificationsdata) {
225 | console.log("Certifications data exists:", certificationsdata);
226 | console.log("Section order includes certifications:", sectionOrder.includes("certifications"));
227 | console.log("Ordered sections:", orderedSections);
228 | }
229 |
230 | return (
231 |
232 | {/* Header Section */}
233 |
234 |
{namedata}
235 |
{positiondata}
236 |
246 |
268 |
269 |
270 | {/* Draggable Sections */}
271 |
272 |
273 | {(provided) => (
274 |
275 | {orderedSections.map((section, index) => (
276 |
277 | {(provided, snapshot) => (
278 |
284 | {renderSection(section)}
285 |
286 | )}
287 |
288 | ))}
289 | {provided.placeholder}
290 |
291 | )}
292 |
293 |
294 |
295 | );
296 | };
297 |
298 | export default TemplateTwo;
--------------------------------------------------------------------------------
/components/ui/sparkles.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useId, useMemo } from "react";
3 | import { useEffect, useState } from "react";
4 | import Particles, { initParticlesEngine } from "@tsparticles/react";
5 | import type { Container, SingleOrMultiple } from "@tsparticles/engine";
6 | import { loadSlim } from "@tsparticles/slim";
7 | import { cn } from "@/lib/utils";
8 | import { motion, useAnimation } from "framer-motion";
9 |
10 | type ParticlesProps = {
11 | id?: string;
12 | className?: string;
13 | background?: string;
14 | particleSize?: number;
15 | minSize?: number;
16 | maxSize?: number;
17 | speed?: number;
18 | particleColor?: string;
19 | particleDensity?: number;
20 | };
21 | export const SparklesCore = (props: ParticlesProps) => {
22 | const {
23 | id,
24 | className,
25 | background,
26 | minSize,
27 | maxSize,
28 | speed,
29 | particleColor,
30 | particleDensity,
31 | } = props;
32 | const [init, setInit] = useState(false);
33 | useEffect(() => {
34 | initParticlesEngine(async (engine) => {
35 | await loadSlim(engine);
36 | }).then(() => {
37 | setInit(true);
38 | });
39 | }, []);
40 | const controls = useAnimation();
41 |
42 | const particlesLoaded = async (container?: Container) => {
43 | if (container) {
44 | controls.start({
45 | opacity: 1,
46 | transition: {
47 | duration: 1,
48 | },
49 | });
50 | }
51 | };
52 |
53 | const generatedId = useId();
54 | return (
55 |
56 | {init && (
57 | | undefined,
161 | },
162 | groups: {},
163 | move: {
164 | angle: {
165 | offset: 0,
166 | value: 90,
167 | },
168 | attract: {
169 | distance: 200,
170 | enable: false,
171 | rotate: {
172 | x: 3000,
173 | y: 3000,
174 | },
175 | },
176 | center: {
177 | x: 50,
178 | y: 50,
179 | mode: "percent",
180 | radius: 0,
181 | },
182 | decay: 0,
183 | distance: {},
184 | direction: "none",
185 | drift: 0,
186 | enable: true,
187 | gravity: {
188 | acceleration: 9.81,
189 | enable: false,
190 | inverse: false,
191 | maxSpeed: 50,
192 | },
193 | path: {
194 | clamp: true,
195 | delay: {
196 | value: 0,
197 | },
198 | enable: false,
199 | options: {},
200 | },
201 | outModes: {
202 | default: "out",
203 | },
204 | random: false,
205 | size: false,
206 | speed: {
207 | min: 0.1,
208 | max: 1,
209 | },
210 | spin: {
211 | acceleration: 0,
212 | enable: false,
213 | },
214 | straight: false,
215 | trail: {
216 | enable: false,
217 | length: 10,
218 | fill: {},
219 | },
220 | vibrate: false,
221 | warp: false,
222 | },
223 | number: {
224 | density: {
225 | enable: true,
226 | width: 400,
227 | height: 400,
228 | },
229 | limit: {
230 | mode: "delete",
231 | value: 0,
232 | },
233 | value: particleDensity || 120,
234 | },
235 | opacity: {
236 | value: {
237 | min: 0.1,
238 | max: 1,
239 | },
240 | animation: {
241 | count: 0,
242 | enable: true,
243 | speed: speed || 4,
244 | decay: 0,
245 | delay: 0,
246 | sync: false,
247 | mode: "auto",
248 | startValue: "random",
249 | destroy: "none",
250 | },
251 | },
252 | reduceDuplicates: false,
253 | shadow: {
254 | blur: 0,
255 | color: {
256 | value: "#000",
257 | },
258 | enable: false,
259 | offset: {
260 | x: 0,
261 | y: 0,
262 | },
263 | },
264 | shape: {
265 | close: true,
266 | fill: true,
267 | options: {},
268 | type: "circle",
269 | },
270 | size: {
271 | value: {
272 | min: minSize || 1,
273 | max: maxSize || 3,
274 | },
275 | animation: {
276 | count: 0,
277 | enable: false,
278 | speed: 5,
279 | decay: 0,
280 | delay: 0,
281 | sync: false,
282 | mode: "auto",
283 | startValue: "random",
284 | destroy: "none",
285 | },
286 | },
287 | stroke: {
288 | width: 0,
289 | },
290 | zIndex: {
291 | value: 0,
292 | opacityRate: 1,
293 | sizeRate: 1,
294 | velocityRate: 1,
295 | },
296 | destroy: {
297 | bounds: {},
298 | mode: "none",
299 | split: {
300 | count: 1,
301 | factor: {
302 | value: 3,
303 | },
304 | rate: {
305 | value: {
306 | min: 4,
307 | max: 9,
308 | },
309 | },
310 | sizeOffset: true,
311 | },
312 | },
313 | roll: {
314 | darken: {
315 | enable: false,
316 | value: 0,
317 | },
318 | enable: false,
319 | enlighten: {
320 | enable: false,
321 | value: 0,
322 | },
323 | mode: "vertical",
324 | speed: 25,
325 | },
326 | tilt: {
327 | value: 0,
328 | animation: {
329 | enable: false,
330 | speed: 0,
331 | decay: 0,
332 | sync: false,
333 | },
334 | direction: "clockwise",
335 | enable: false,
336 | },
337 | twinkle: {
338 | lines: {
339 | enable: false,
340 | frequency: 0.05,
341 | opacity: 1,
342 | },
343 | particles: {
344 | enable: false,
345 | frequency: 0.05,
346 | opacity: 1,
347 | },
348 | },
349 | wobble: {
350 | distance: 5,
351 | enable: false,
352 | speed: {
353 | angle: 50,
354 | move: 10,
355 | },
356 | },
357 | life: {
358 | count: 0,
359 | delay: {
360 | value: 0,
361 | sync: false,
362 | },
363 | duration: {
364 | value: 0,
365 | sync: false,
366 | },
367 | },
368 | rotate: {
369 | value: 0,
370 | animation: {
371 | enable: false,
372 | speed: 0,
373 | decay: 0,
374 | sync: false,
375 | },
376 | direction: "clockwise",
377 | path: false,
378 | },
379 | orbit: {
380 | animation: {
381 | count: 0,
382 | enable: false,
383 | speed: 1,
384 | decay: 0,
385 | delay: 0,
386 | sync: false,
387 | },
388 | enable: false,
389 | opacity: 1,
390 | rotation: {
391 | value: 45,
392 | },
393 | width: 1,
394 | },
395 | links: {
396 | blink: false,
397 | color: {
398 | value: "#fff",
399 | },
400 | consent: false,
401 | distance: 100,
402 | enable: false,
403 | frequency: 1,
404 | opacity: 1,
405 | shadow: {
406 | blur: 5,
407 | color: {
408 | value: "#000",
409 | },
410 | enable: false,
411 | },
412 | triangles: {
413 | enable: false,
414 | frequency: 1,
415 | },
416 | width: 1,
417 | warp: false,
418 | },
419 | repulse: {
420 | value: 0,
421 | enabled: false,
422 | distance: 1,
423 | duration: 1,
424 | factor: 1,
425 | speed: 1,
426 | },
427 | },
428 | detectRetina: true,
429 | }}
430 | />
431 | )}
432 |
433 | );
434 | };
435 |
--------------------------------------------------------------------------------
/components/preview/Preview.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | FaGithub,
3 | FaLinkedin,
4 | FaTwitter,
5 | FaFacebook,
6 | FaInstagram,
7 | FaYoutube,
8 | FaExchangeAlt,
9 | FaExternalLinkAlt,
10 | } from "react-icons/fa";
11 | import { MdEmail, MdLocationOn, MdPhone } from "react-icons/md";
12 | import { CgWebsite } from "react-icons/cg";
13 | import Skills from "./Skills";
14 | import DateRange from "../utility/DateRange";
15 | import ContactInfo from "./ContactInfo";
16 | import Image from "next/image";
17 | import Link from "next/link";
18 | import React, { useContext, useState, useEffect } from "react";
19 | import { ResumeContext } from "../../pages/builder";
20 | import dynamic from "next/dynamic";
21 | import Language from "./Language";
22 | import Certification from "./Certification";
23 | import TemplateTwo from "./TemplateTwo";
24 |
25 | const DragDropContext = dynamic(
26 | () =>
27 | import("react-beautiful-dnd").then((mod) => {
28 | return mod.DragDropContext;
29 | }),
30 | { ssr: false }
31 | );
32 | const Droppable = dynamic(
33 | () =>
34 | import("react-beautiful-dnd").then((mod) => {
35 | return mod.Droppable;
36 | }),
37 | { ssr: false }
38 | );
39 | const Draggable = dynamic(
40 | () =>
41 | import("react-beautiful-dnd").then((mod) => {
42 | return mod.Draggable;
43 | }),
44 | { ssr: false }
45 | );
46 |
47 | const Preview = () => {
48 | const { resumeData, setResumeData } = useContext(ResumeContext);
49 | const [currentTemplate, setCurrentTemplate] = useState("template1");
50 |
51 | // Initialize sectionOrder with all sections including certifications
52 | const defaultSections = [
53 | "summary",
54 | "education",
55 | "projects",
56 | "experience",
57 | "skills",
58 | "softskills",
59 | "languages",
60 | "certifications"
61 | ];
62 |
63 | const [sectionOrder, setSectionOrder] = useState(defaultSections);
64 |
65 | const icons = [
66 | { name: "github", icon: },
67 | { name: "linkedin", icon: },
68 | { name: "twitter", icon: },
69 | { name: "facebook", icon: },
70 | { name: "instagram", icon: },
71 | { name: "youtube", icon: },
72 | { name: "website", icon: },
73 | ];
74 |
75 | const onDragEnd = (result) => {
76 | const { destination, source } = result;
77 |
78 | if (!destination) return;
79 |
80 | if (
81 | destination.droppableId === source.droppableId &&
82 | destination.index === source.index
83 | )
84 | return;
85 |
86 | if (source.droppableId === "work-experience") {
87 | const newWorkExperience = [...resumeData.workExperience];
88 | const [removed] = newWorkExperience.splice(source.index, 1);
89 | newWorkExperience.splice(destination.index, 0, removed);
90 | setResumeData({ ...resumeData, workExperience: newWorkExperience });
91 | }
92 |
93 | if (source.droppableId.includes("WORK_EXPERIENCE_KEY_ACHIEVEMENT")) {
94 | const newWorkExperience = [...resumeData.workExperience];
95 | const workExperienceIndex = parseInt(source.droppableId.split("-")[1]);
96 | const keyAchievements =
97 | newWorkExperience[workExperienceIndex].keyAchievements.split("\n");
98 | const [removed] = keyAchievements.splice(source.index, 1);
99 | keyAchievements.splice(destination.index, 0, removed);
100 | newWorkExperience[workExperienceIndex].keyAchievements =
101 | keyAchievements.join("\n");
102 | setResumeData({ ...resumeData, workExperience: newWorkExperience });
103 | }
104 |
105 | if (source.droppableId === "skills") {
106 | const newSkills = [...resumeData.skills];
107 | const [removed] = newSkills.splice(source.index, 1);
108 | newSkills.splice(destination.index, 0, removed);
109 | setResumeData({ ...resumeData, skills: newSkills });
110 | }
111 |
112 | if (source.droppableId.includes("projects")) {
113 | const newProjects = [...resumeData.projects];
114 | const [removed] = newProjects.splice(source.index, 1);
115 | newProjects.splice(destination.index, 0, removed);
116 | setResumeData({ ...resumeData, projects: newProjects });
117 | }
118 |
119 | if (source.droppableId.includes("PROJECTS_KEY_ACHIEVEMENT")) {
120 | const newProjects = [...resumeData.projects];
121 | const projectIndex = parseInt(source.droppableId.split("-")[1]);
122 | const keyAchievements =
123 | newProjects[projectIndex].keyAchievements.split("\n");
124 | const [removed] = keyAchievements.splice(source.index, 1);
125 | keyAchievements.splice(destination.index, 0, removed);
126 | newProjects[projectIndex].keyAchievements = keyAchievements.join("\n");
127 | setResumeData({ ...resumeData, projects: newProjects });
128 | }
129 | };
130 |
131 | const handleTemplateTwoDragEnd = (result) => {
132 | if (!result.destination) return;
133 |
134 | const items = Array.from(sectionOrder);
135 | const [reorderedItem] = items.splice(result.source.index, 1);
136 | items.splice(result.destination.index, 0, reorderedItem);
137 |
138 |
139 | setSectionOrder(items);
140 | localStorage.setItem('sectionOrder', JSON.stringify(items));
141 | };
142 |
143 | useEffect(() => {
144 | const savedOrder = localStorage.getItem('sectionOrder');
145 | if (savedOrder) {
146 | // Ensure certifications is included in the saved order
147 | const parsedOrder = JSON.parse(savedOrder);
148 | if (!parsedOrder.includes("certifications")) {
149 | parsedOrder.push("certifications");
150 | }
151 | setSectionOrder(parsedOrder);
152 | } else {
153 | // If no saved order, use default sections
154 | localStorage.setItem('sectionOrder', JSON.stringify(defaultSections));
155 | }
156 | }, []);
157 |
158 | return (
159 |
160 |
setCurrentTemplate(currentTemplate === "template1" ? "template2" : "template1")}
162 | className="absolute top-4 right-4 z-50 p-2 bg-zinc-800 text-white rounded hover:bg-zinc-700 transition-colors exclude-print"
163 | title="Switch Template"
164 | >
165 |
166 |
167 |
168 |
169 | {currentTemplate === "template1" ? (
170 |
171 |
172 |
{resumeData.name}
173 |
{resumeData.position}
174 |
}
181 | emailicon={
}
182 | addressicon={
}
183 | />
184 |
206 |
207 |
208 | {/* two column start */}
209 |
210 |
211 | {resumeData.summary.length > 0 && (
212 |
213 |
214 | Summary
215 |
216 |
{resumeData.summary}
217 |
218 | )}
219 |
220 | {resumeData.education.length > 0 && (
221 |
222 |
223 | Education
224 |
225 | {resumeData.education.map((item, index) => (
226 |
227 |
{item.degree}
228 |
{item.school}
229 |
234 |
235 | ))}
236 |
237 | )}
238 |
239 |
240 | {(provided) => (
241 |
242 | {resumeData.skills.map((skill, index) => (
243 |
248 | {(provided, snapshot) => (
249 |
258 |
259 |
260 | )}
261 |
262 | ))}
263 | {provided.placeholder}
264 |
265 | )}
266 |
267 |
268 |
272 |
273 |
274 | {resumeData.workExperience.length > 0 && (
275 |
276 | {(provided) => (
277 |
278 |
283 | Work Experience
284 |
285 | {resumeData.workExperience.map((item, index) => (
286 |
291 | {(provided, snapshot) => (
292 |
301 |
{item.company}
302 |
{item.position}
303 |
308 |
{item.description}
309 |
313 | {(provided) => (
314 |
319 | {typeof item.keyAchievements === "string" &&
320 | item.keyAchievements
321 | .split("\n")
322 | .map((achievement, subIndex) => (
323 |
328 | {(provided, snapshot) => (
329 |
340 | {achievement}
341 |
342 | )}
343 |
344 | ))}
345 | {provided.placeholder}
346 |
347 | )}
348 |
349 |
350 | )}
351 |
352 | ))}
353 | {provided.placeholder}
354 |
355 | )}
356 |
357 | )}
358 | {resumeData.projects.length > 0 && (
359 |
360 | {(provided) => (
361 |
362 |
367 | Projects
368 |
369 | {resumeData.projects.map((item, index) => (
370 |
375 | {(provided, snapshot) => (
376 |
385 |
386 |
{item.name}
387 | {item.link && (
388 |
395 |
396 |
397 | )}
398 |
399 |
404 |
{item.description}
405 |
409 | {(provided) => (
410 |
415 | {typeof item.keyAchievements === "string" &&
416 | item.keyAchievements
417 | .split("\n")
418 | .map((achievement, subIndex) => (
419 |
424 | {(provided, snapshot) => (
425 |
436 | {achievement}
437 |
438 | )}
439 |
440 | ))}
441 | {provided.placeholder}
442 |
443 | )}
444 |
445 |
446 | )}
447 |
448 | ))}
449 | {provided.placeholder}
450 |
451 | )}
452 |
453 | )}
454 |
455 |
456 |
457 | ) : (
458 | }
465 | emailicon={ }
466 | addressicon={ }
467 | summarydata={resumeData.summary}
468 | educationdata={resumeData.education}
469 | projectsdata={resumeData.projects}
470 | workExperiencedata={resumeData.workExperience}
471 | skillsdata={resumeData.skills}
472 | languagesdata={resumeData.languages}
473 | certificationsdata={resumeData.certifications}
474 | sectionOrder={sectionOrder}
475 | onDragEnd={handleTemplateTwoDragEnd}
476 | resumeData={resumeData}
477 | setResumeData={setResumeData}
478 | />
479 | )}
480 |
481 |
482 |
483 | );
484 | };
485 |
486 | const A4PageWrapper = ({ children }) => {
487 |
488 | const alertA4Size = () => {
489 | const preview = document.querySelector(".preview");
490 | const previewHeight = preview.offsetHeight;
491 | if (previewHeight > 1122) {
492 | alert("A4 size exceeded");
493 | }
494 | };
495 |
496 | return {children}
;
497 | };
498 |
499 | export default Preview;
--------------------------------------------------------------------------------