commit 2dd24927bc09e5c43d70ba2f39d29300327e1cd9 Author: nishant7929 Date: Mon Jul 7 00:52:41 2025 +0530 init commit diff --git a/Steps to follow.pdf b/Steps to follow.pdf new file mode 100644 index 0000000..6fb0786 Binary files /dev/null and b/Steps to follow.pdf differ diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..d958e46 --- /dev/null +++ b/backend/.env @@ -0,0 +1,3 @@ +MONGO_URI=mongodb://localhost:27017/mern_resume_builder +JWT_SECRET=9cd1f9e482ac014933a12ca44f3b1decfb2ee134ecd66203b4ad46a991cf216e69fd0058fb43741705724c32b31965d9416fc871ceafc48a624ab41dbd780905 +PORT=8000 \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/backend/config/db.js b/backend/config/db.js new file mode 100644 index 0000000..d973893 --- /dev/null +++ b/backend/config/db.js @@ -0,0 +1,13 @@ +const mongoose = require("mongoose"); + +const connectDB = async () => { + try { + await mongoose.connect(process.env.MONGO_URI, {}); + console.log("MongoDB connected"); + } catch (err) { + console.error("Error connecting to MongoDB", err); + process.exit(1); + } +}; + +module.exports = connectDB; diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js new file mode 100644 index 0000000..393a47f --- /dev/null +++ b/backend/controllers/authController.js @@ -0,0 +1,94 @@ +const User = require("../models/User"); +const bcrypt = require("bcryptjs"); +const jwt = require("jsonwebtoken"); + +// Generate JWT Token +const generateToken = (userId) => { + return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: "7d" }); +}; + +// @desc Register a new user +// @route POST /api/auth/register +// @access Public +const registerUser = async (req, res) => { + try { + const { name, email, password, profileImageUrl } = req.body; + + // Check if user already exists + const userExists = await User.findOne({ email }); + if (userExists) { + return res.status(400).json({ message: "User already exists" }); + } + + // Hash password + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + // Create new user + const user = await User.create({ + name, + email, + password: hashedPassword, + profileImageUrl, + }); + + // Return user data with JWT + res.status(201).json({ + _id: user._id, + name: user.name, + email: user.email, + profileImageUrl: user.profileImageUrl, + token: generateToken(user._id), + }); + } catch (error) { + res.status(500).json({ message: "Server error", error: error.message }); + } +}; + +// @desc Login user +// @route POST /api/auth/login +// @access Public +const loginUser = async (req, res) => { + try { + const { email, password } = req.body; + + const user = await User.findOne({ email }); + if (!user) { + return res.status(500).json({ message: "Invalid email or password" }); + } + + // Compare password + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + return res.status(500).json({ message: "Invalid email or password" }); + } + + // Return user data with JWT + res.json({ + _id: user._id, + name: user.name, + email: user.email, + profileImageUrl: user.profileImageUrl, + token: generateToken(user._id), + }); + } catch (error) { + res.status(500).json({ message: "Server error", error: error.message }); + } +}; + +// @desc Get user profile +// @route GET /api/auth/profile +// @access Private (Requires JWT) +const getUserProfile = async (req, res) => { + try { + const user = await User.findById(req.user.id).select("-password"); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + res.json(user); + } catch (error) { + res.status(500).json({ message: "Server error", error: error.message }); + } +}; + +module.exports = { registerUser, loginUser, getUserProfile }; diff --git a/backend/controllers/resumeController.js b/backend/controllers/resumeController.js new file mode 100644 index 0000000..51cb4a9 --- /dev/null +++ b/backend/controllers/resumeController.js @@ -0,0 +1,204 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const Resume = require("../models/Resume"); + +// @desc Create a new resume +// @route POST /api/resumes +// @access Private +const createResume = async (req, res) => { + try { + const { title } = req.body; + + // Default template + const defaultResumeData = { + profileInfo: { + profileImg: null, + previewUrl: "", + fullName: "", + designation: "", + summary: "", + }, + contactInfo: { + email: "", + phone: "", + location: "", + linkedin: "", + github: "", + website: "", + }, + workExperience: [ + { + company: "", + role: "", + startDate: "", + endDate: "", + description: "", + }, + ], + education: [ + { + degree: "", + institution: "", + startDate: "", + endDate: "", + }, + ], + skills: [ + { + name: "", + progress: 0, + }, + ], + projects: [ + { + title: "", + description: "", + github: "", + liveDemo: "", + }, + ], + certifications: [ + { + title: "", + issuer: "", + year: "", + }, + ], + languages: [ + { + name: "", + progress: 0, + }, + ], + interests: [""], + }; + + const newResume = await Resume.create({ + userId: req.user._id, + title, + ...defaultResumeData, + }); + + res.status(201).json(newResume); + } catch (error) { + res + .status(500) + .json({ message: "Failed to create resume", error: error.message }); + } +}; + +// @desc Get all resumes for logged-in user +// @route GET /api/resumes +// @access Private +const getUserResumes = async (req, res) => { + try { + const resumes = await Resume.find({ userId: req.user._id }).sort({ + updatedAt: -1, + }); + res.json(resumes); + } catch (error) { + res + .status(500) + .json({ message: "Failed to create resume", error: error.message }); + } +}; + +// @desc Get single resume by ID +// @route GET /api/resumes/:id +// @access Private +const getResumeById = async (req, res) => { + try { + const resume = await Resume.findOne({ _id: req.params.id, userId: req.user._id }); + + if (!resume) { + return res.status(404).json({ message: "Resume not found" }); + } + + res.json(resume); + } catch (error) { + res + .status(500) + .json({ message: "Failed to create resume", error: error.message }); + } +}; + +// @desc Update a resume +// @route PUT /api/resumes/:id +// @access Private +const updateResume = async (req, res) => { + try { + const resume = await Resume.findOne({ + _id: req.params.id, + userId: req.user._id, + }); + + if (!resume) { + return res.status(404).json({ message: "Resume not found or unauthorized" }); + } + + // Merge updates from req.body into existing resume + Object.assign(resume, req.body); + + // Save updated resume + const savedResume = await resume.save(); + + res.json(savedResume); + } catch (error) { + res + .status(500) + .json({ message: "Failed to create resume", error: error.message }); + } +}; + +// @desc Delete a resume +// @route DELETE /api/resumes/:id +// @access Private +const deleteResume = async (req, res) => { + try { + const resume = await Resume.findOne({ + _id: req.params.id, + userId: req.user._id, + }); + + if (!resume) { + return res.status(404).json({ message: "Resume not found or unauthorized" }); + } + + // Delete thumbnailLink and profilePreviewUrl images from uploads folder + const uploadsFolder = path.join(__dirname, '..', 'uploads'); + const baseUrl = `${req.protocol}://${req.get("host")}`; + + if(resume.thumbnailLink){ + const oldThumbnail = path.join(uploadsFolder, path.basename(resume.thumbnailLink)); + if (fs.existsSync(oldThumbnail)) fs.unlinkSync(oldThumbnail); + } + + if(resume.profileInfo?.profilePreviewUrl){ + const oldProfile = path.join(uploadsFolder, path.basename(resume.profileInfo.profilePreviewUrl)); + if (fs.existsSync(oldProfile)) fs.unlinkSync(oldProfile); + } + + const deleted = await Resume.findOneAndDelete({ + _id: req.params.id, + userId: req.user._id, + }); + + if (!deleted) { + return res.status(404).json({ message: "Resume not found or unauthorized" }); + } + + res.json({ message: "Resume deleted successfully" }); + } catch (error) { + res + .status(500) + .json({ message: "Failed to create resume", error: error.message }); + } +}; + +module.exports = { + createResume, + getUserResumes, + getResumeById, + updateResume, + deleteResume, +}; diff --git a/backend/controllers/uploadImages.js b/backend/controllers/uploadImages.js new file mode 100644 index 0000000..6e7f86a --- /dev/null +++ b/backend/controllers/uploadImages.js @@ -0,0 +1,58 @@ +const fs = require("fs"); +const path = require("path"); +const Resume = require("../models/Resume"); +const upload = require("../middlewares/uploadMiddleware"); + +const uploadResumeImages = async (req, res) => { + try { + upload.fields([{ name: 'thumbnail' }, { name: 'profileImage' }])(req, res, async (err) => { + if (err) { + return res.status(400).json({ message: "File upload failed", error: err.message }); + } + + const resumeId = req.params.id; + const resume = await Resume.findOne({ _id: resumeId, userId: req.user._id }); + + if (!resume) { + return res.status(404).json({ message: "Resume not found or unauthorized" }); + } + + const uploadsFolder = path.join(__dirname, '..', 'uploads'); + const baseUrl = `${req.protocol}://${req.get("host")}`; + + const newThumbnail = req.files.thumbnail?.[0]; + const newProfileImage = req.files.profileImage?.[0]; + + // If new thumbnail uploaded, delete old one + if (newThumbnail) { + if(resume.thumbnailLink){ + const oldThumbnail = path.join(uploadsFolder, path.basename(resume.thumbnailLink)); + if (fs.existsSync(oldThumbnail)) fs.unlinkSync(oldThumbnail); + } + resume.thumbnailLink = `${baseUrl}/uploads/${newThumbnail.filename}`; + } + + // If new profile image uploaded, delete old one + if (newProfileImage) { + if(resume.profileInfo?.profilePreviewUrl){ + const oldProfile = path.join(uploadsFolder, path.basename(resume.profileInfo.profilePreviewUrl)); + if (fs.existsSync(oldProfile)) fs.unlinkSync(oldProfile); + } + resume.profileInfo.profilePreviewUrl = `${baseUrl}/uploads/${newProfileImage.filename}`; + } + + await resume.save(); + + res.status(200).json({ + message: "Images uploaded successfully", + thumbnailLink: resume.thumbnailLink, + profilePreviewUrl: resume.profileInfo.profilePreviewUrl, + }); + }); + } catch (err) { + console.error("Error uploading images:", err); + res.status(500).json({ message: "Failed to upload images", error: err.message }); + } +}; + +module.exports = { uploadResumeImages }; diff --git a/backend/middlewares/authMiddleware.js b/backend/middlewares/authMiddleware.js new file mode 100644 index 0000000..1604991 --- /dev/null +++ b/backend/middlewares/authMiddleware.js @@ -0,0 +1,22 @@ +const jwt = require("jsonwebtoken"); +const User = require("../models/User"); + +// Middleware to protect routes +const protect = async (req, res, next) => { + try { + let token = req.headers.authorization; + + if (token && token.startsWith("Bearer")) { + token = token.split(" ")[1]; // Extract token + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = await User.findById(decoded.id).select("-password"); + next(); + } else { + res.status(401).json({ message: "Not authorized, no token" }); + } + } catch (error) { + res.status(401).json({ message: "Token failed", error: error.message }); + } +}; + +module.exports = { protect }; diff --git a/backend/middlewares/uploadMiddleware.js b/backend/middlewares/uploadMiddleware.js new file mode 100644 index 0000000..996d28e --- /dev/null +++ b/backend/middlewares/uploadMiddleware.js @@ -0,0 +1,25 @@ +const multer = require('multer'); + +// Configure storage +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, 'uploads/'); + }, + filename: (req, file, cb) => { + cb(null, `${Date.now()}-${file.originalname}`); + }, +}); + +// File filter +const fileFilter = (req, file, cb) => { + const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg']; + if (allowedTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('Only .jpeg, .jpg and .png formats are allowed'), false); + } +}; + +const upload = multer({ storage, fileFilter }); + +module.exports = upload; diff --git a/backend/models/Resume.js b/backend/models/Resume.js new file mode 100644 index 0000000..5d916a4 --- /dev/null +++ b/backend/models/Resume.js @@ -0,0 +1,86 @@ +const mongoose = require("mongoose"); + +const ResumeSchema = new mongoose.Schema( + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + title: { + type: String, + required: true, + }, + thumbnailLink: { + type: String, + }, + template:{ + theme: String, + colorPalette: [String] + }, + profileInfo: { + profilePreviewUrl: String, + fullName: String, + designation: String, + summary: String, + }, + contactInfo: { + email: String, + phone: String, + location: String, + linkedin: String, + github: String, + website: String, + }, + workExperience: [ + { + company: String, + role: String, + startDate: String, + endDate: String, + description: String, + }, + ], + education: [ + { + degree: String, + institution: String, + startDate: String, + endDate: String, + }, + ], + skills: [ + { + name: String, + progress: Number, + }, + ], + projects: [ + { + title: String, + description: String, + github: String, + liveDemo: String, + }, + ], + certifications: [ + { + title: String, + issuer: String, + year: String, + }, + ], + languages: [ + { + name: String, + progress: Number, + }, + ], + interests: [String], + }, + { + timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" }, + } +); + +module.exports = mongoose.model("Resume", ResumeSchema); diff --git a/backend/models/User.js b/backend/models/User.js new file mode 100644 index 0000000..c0a888b --- /dev/null +++ b/backend/models/User.js @@ -0,0 +1,13 @@ +const mongoose = require("mongoose"); + +const UserSchema = new mongoose.Schema( + { + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true }, + profileImageUrl: { type: String, default: null }, + }, + { timestamps: true } +); + +module.exports = mongoose.model("User", UserSchema); diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..0d7734e --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1723 @@ +{ + "name": "resume-builder", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "resume-builder", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "bcryptjs": "^3.0.2", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.13.2", + "multer": "^1.4.5-lts.2" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", + "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mongodb": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", + "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.13.2.tgz", + "integrity": "sha512-riCBqZmNkYBWjXpM3qWLDQw7QmTKsVZDPhLXFJqC87+OjocEVpvS3dA2BPPUiLAu+m0/QmEj5pSXKhH+/DgerQ==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.15.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..2a6d2fc --- /dev/null +++ b/backend/package.json @@ -0,0 +1,26 @@ +{ + "name": "resume-builder", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "bcryptjs": "^3.0.2", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.13.2", + "multer": "^1.4.5-lts.2" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js new file mode 100644 index 0000000..053f55f --- /dev/null +++ b/backend/routes/authRoutes.js @@ -0,0 +1,23 @@ +const express = require("express"); +const { registerUser, loginUser, getUserProfile } = require("../controllers/authController"); +const { protect } = require("../middlewares/authMiddleware"); +const upload = require('../middlewares/uploadMiddleware') + +const router = express.Router(); + +// Auth Routes +router.post("/register", registerUser); // Register User +router.post("/login", loginUser); // Login User +router.get("/profile", protect, getUserProfile); // Get User Profile + +router.post("/upload-image", upload.single("image"), (req, res) => { + if (!req.file) { + return res.status(400).json({ message: "No file uploaded" }); + } + const imageUrl = `${req.protocol}://${req.get("host")}/uploads/${ + req.file.filename + }`; + res.status(200).json({ imageUrl }); +}); + +module.exports = router; diff --git a/backend/routes/resumeRoutes.js b/backend/routes/resumeRoutes.js new file mode 100644 index 0000000..9dc30a0 --- /dev/null +++ b/backend/routes/resumeRoutes.js @@ -0,0 +1,23 @@ +const express = require("express"); +const { + createResume, + getUserResumes, + getResumeById, + updateResume, + deleteResume, +} = require("../controllers/resumeController"); +const { protect } = require("../middlewares/authMiddleware"); +const { uploadResumeImages } = require("../controllers/uploadImages"); + +const router = express.Router(); + + +router.post("/", protect, createResume); // Create Resume +router.get("/", protect, getUserResumes); // Get Resume +router.get("/:id", protect, getResumeById); // Get Resume By ID +router.put("/:id", protect, updateResume); // Update Resume +router.put("/:id/upload-images", protect, uploadResumeImages); + +router.delete("/:id", protect, deleteResume); // Delete Resume + +module.exports = router; diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..e915b69 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,45 @@ +require("dotenv").config(); +const express = require("express"); +const cors = require("cors"); +const path = require("path"); +const connectDB = require('./config/db') + +const authRoutes = require('./routes/authRoutes') +const resumeRoutes = require('./routes/resumeRoutes') + +const app = express(); + +// Middleware to handle CORS +app.use( + cors({ + origin: "*", + methods: ["GET", "POST", "PUT", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization"], + }) +); + +// Connect Database +connectDB(); + +// Middleware +app.use(express.json()); + + +// Routes +app.use("/api/auth", authRoutes); +app.use("/api/resume", resumeRoutes); + +// Serve uploads folder +app.use( + "/uploads", + express.static(path.join(__dirname, "uploads"), { + setHeaders: (res, path) => { + res.set("Access-Control-Allow-Origin", "http://localhost:5173"); + }, + }) +); + + +// Start Server +const PORT = process.env.PORT || 5000; +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); \ No newline at end of file diff --git a/backend/uploads/1751829475177-image (1) (1).png b/backend/uploads/1751829475177-image (1) (1).png new file mode 100644 index 0000000..a129709 Binary files /dev/null and b/backend/uploads/1751829475177-image (1) (1).png differ diff --git a/backend/uploads/1751829475190-resume-686ac60edf047948687462d7.png b/backend/uploads/1751829475190-resume-686ac60edf047948687462d7.png new file mode 100644 index 0000000..bb32751 Binary files /dev/null and b/backend/uploads/1751829475190-resume-686ac60edf047948687462d7.png differ diff --git a/frontend/.DS_Store b/frontend/.DS_Store new file mode 100644 index 0000000..f37261a Binary files /dev/null and b/frontend/.DS_Store differ diff --git a/frontend/resume-builder/.gitignore b/frontend/resume-builder/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/resume-builder/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/resume-builder/README.md b/frontend/resume-builder/README.md new file mode 100644 index 0000000..7059a96 --- /dev/null +++ b/frontend/resume-builder/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/frontend/resume-builder/eslint.config.js b/frontend/resume-builder/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/frontend/resume-builder/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/frontend/resume-builder/index.html b/frontend/resume-builder/index.html new file mode 100644 index 0000000..6fbe1dc --- /dev/null +++ b/frontend/resume-builder/index.html @@ -0,0 +1,13 @@ + + + + + + + Resume Builder + + +
+ + + diff --git a/frontend/resume-builder/package-lock.json b/frontend/resume-builder/package-lock.json new file mode 100644 index 0000000..10696f2 --- /dev/null +++ b/frontend/resume-builder/package-lock.json @@ -0,0 +1,3706 @@ +{ + "name": "resume-builder", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "resume-builder", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/vite": "^4.1.4", + "axios": "^1.8.4", + "html2canvas": "^1.4.1", + "moment": "^2.30.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", + "react-router-dom": "^7.5.1", + "react-to-print": "^3.0.6", + "tailwindcss": "^4.1.4" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", + "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz", + "integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "tailwindcss": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.4.tgz", + "integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-x64": "4.1.4", + "@tailwindcss/oxide-freebsd-x64": "4.1.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-x64-musl": "4.1.4", + "@tailwindcss/oxide-wasm32-wasi": "4.1.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.4.tgz", + "integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.4.tgz", + "integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.4.tgz", + "integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.4.tgz", + "integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.4.tgz", + "integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.4.tgz", + "integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.4.tgz", + "integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.4.tgz", + "integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.4.tgz", + "integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.4.tgz", + "integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", + "@emnapi/wasi-threads": "^1.0.1", + "@napi-rs/wasm-runtime": "^0.2.8", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.4.tgz", + "integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.4.tgz", + "integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.4.tgz", + "integrity": "sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.4", + "@tailwindcss/oxide": "4.1.4", + "tailwindcss": "4.1.4" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", + "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.140", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz", + "integrity": "sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", + "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.25.1", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz", + "integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz", + "integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-to-print": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-3.0.6.tgz", + "integrity": "sha512-K/jFxkUifbfVnu1XyinM6AB6zAq0VMw0lH/6WJpkdlChoqqvEOE/BGOxYN2xOmu8f72isTTU5DNatK/j0Lfc+Q==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ~19" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", + "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/resume-builder/package.json b/frontend/resume-builder/package.json new file mode 100644 index 0000000..2bae355 --- /dev/null +++ b/frontend/resume-builder/package.json @@ -0,0 +1,36 @@ +{ + "name": "resume-builder", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.4", + "axios": "^1.8.4", + "html2canvas": "^1.4.1", + "moment": "^2.30.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", + "react-router-dom": "^7.5.1", + "react-to-print": "^3.0.6", + "tailwindcss": "^4.1.4" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "vite": "^6.3.1" + } +} diff --git a/frontend/resume-builder/public/vite.svg b/frontend/resume-builder/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/resume-builder/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resume-builder/src/App.jsx b/frontend/resume-builder/src/App.jsx new file mode 100644 index 0000000..6bd7d0a --- /dev/null +++ b/frontend/resume-builder/src/App.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { Toaster } from "react-hot-toast"; +import LandingPage from "./pages/LandingPage"; +import Dashboard from "./pages/Home/Dashboard"; +import EditResume from "./pages/ResumeUpdate/EditResume"; +import UserProvider from "./context/userContext"; + +const App = () => { + return ( + +
+ + + {/* Default Route */} + } /> + } /> + } /> + + +
+ + +
+ ); +}; + +export default App; diff --git a/frontend/resume-builder/src/assets/hero-img.png b/frontend/resume-builder/src/assets/hero-img.png new file mode 100644 index 0000000..ad6babc Binary files /dev/null and b/frontend/resume-builder/src/assets/hero-img.png differ diff --git a/frontend/resume-builder/src/assets/react.svg b/frontend/resume-builder/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/resume-builder/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/resume-builder/src/assets/template-one.png b/frontend/resume-builder/src/assets/template-one.png new file mode 100644 index 0000000..27362e5 Binary files /dev/null and b/frontend/resume-builder/src/assets/template-one.png differ diff --git a/frontend/resume-builder/src/assets/template-three.png b/frontend/resume-builder/src/assets/template-three.png new file mode 100644 index 0000000..bbbdc98 Binary files /dev/null and b/frontend/resume-builder/src/assets/template-three.png differ diff --git a/frontend/resume-builder/src/assets/template-two.png b/frontend/resume-builder/src/assets/template-two.png new file mode 100644 index 0000000..8e80ba2 Binary files /dev/null and b/frontend/resume-builder/src/assets/template-two.png differ diff --git a/frontend/resume-builder/src/components/Cards/ProfileInfoCard.jsx b/frontend/resume-builder/src/components/Cards/ProfileInfoCard.jsx new file mode 100644 index 0000000..d647058 --- /dev/null +++ b/frontend/resume-builder/src/components/Cards/ProfileInfoCard.jsx @@ -0,0 +1,39 @@ +import React, { useContext } from "react"; +import { UserContext } from "../../context/userContext"; +import { useNavigate } from "react-router-dom"; + +const ProfileInfoCard = () => { + const { user, clearUser } = useContext(UserContext); + const navigate = useNavigate(); + + const handelLogout = () => { + localStorage.clear(); + clearUser(); + navigate("/"); + }; + + return ( + user && ( +
+ +
+
+ {user.name || ""} +
+ +
+
+ ) + ); +}; + +export default ProfileInfoCard; diff --git a/frontend/resume-builder/src/components/Cards/ResumeSummaryCard.jsx b/frontend/resume-builder/src/components/Cards/ResumeSummaryCard.jsx new file mode 100644 index 0000000..e70738f --- /dev/null +++ b/frontend/resume-builder/src/components/Cards/ResumeSummaryCard.jsx @@ -0,0 +1,46 @@ +import React, { useEffect, useState } from "react"; +import { getLightColorFromImage } from "../../utils/helper"; + +const ResumeSummaryCard = ({ imgUrl, title, lastUpdated, onSelect }) => { + + const [bgColor, setBgColor] = useState("#ffffff"); + + useEffect(() => { + if (imgUrl) { + getLightColorFromImage(imgUrl) + .then((color) => { + setBgColor(color); + }) + .catch(() => { + setBgColor("#ffffff"); + }); + } + }, [imgUrl]); + + return
+
+ {imgUrl ? ( + + ) : ( +
+ )} +
+ +
+
{title}
+

+ Last Updated: {lastUpdated} +

+
+
+}; + +export default ResumeSummaryCard; diff --git a/frontend/resume-builder/src/components/Cards/TemplateCard.jsx b/frontend/resume-builder/src/components/Cards/TemplateCard.jsx new file mode 100644 index 0000000..6146eba --- /dev/null +++ b/frontend/resume-builder/src/components/Cards/TemplateCard.jsx @@ -0,0 +1,17 @@ +import React from "react"; + +const TemplateCard = ({ thumbnailImg, isSelected, onSelect }) => { + return
+ {thumbnailImg ? ( + + ) : ( +
+ )} +
+}; + +export default TemplateCard; diff --git a/frontend/resume-builder/src/components/Inputs/Input.jsx b/frontend/resume-builder/src/components/Inputs/Input.jsx new file mode 100644 index 0000000..1fa8a2c --- /dev/null +++ b/frontend/resume-builder/src/components/Inputs/Input.jsx @@ -0,0 +1,46 @@ +import React, { useState } from "react"; +import { FaRegEye, FaRegEyeSlash } from "react-icons/fa6"; + +const Input = ({ value, onChange, label, placeholder, type }) => { + const [showPassword, setShowPassword] = useState(false); + + const toggleShowPassword = () => { + setShowPassword(!showPassword); + }; + + return
+ + +
+ onChange(e)} + /> + + {type === "password" && ( + <> + {showPassword ? ( + toggleShowPassword()} + /> + ) : ( + toggleShowPassword()} + /> + )} + + )} +
+
+}; + +export default Input; diff --git a/frontend/resume-builder/src/components/Inputs/ProfilePhotoSelector.jsx b/frontend/resume-builder/src/components/Inputs/ProfilePhotoSelector.jsx new file mode 100644 index 0000000..f89259f --- /dev/null +++ b/frontend/resume-builder/src/components/Inputs/ProfilePhotoSelector.jsx @@ -0,0 +1,77 @@ +import React, { useRef, useState } from 'react' +import { LuUser, LuUpload, LuTrash } from "react-icons/lu"; + +const ProfilePhotoSelector = ({image, setImage, preview, setPreview}) => { + const inputRef = useRef(null); + const [previewUrl, setPreviewUrl] = useState(null); + + const handleImageChange = (event) => { + const file = event.target.files[0]; + if (file) { + // Update the image state + setImage(file); + + // Generate preview URL from the file + const preview = URL.createObjectURL(file); + if(setPreview){ + setPreview(preview) + } + setPreviewUrl(preview); + } + }; + + const handleRemoveImage = () => { + setImage(null); + setPreviewUrl(null); + + if(setPreview){ + setPreview(null) + } + }; + + const onChooseFile = () => { + inputRef.current.click(); + }; + return ( +
+ + + {!image ? ( +
+ + + +
+ ) : ( +
+ profile photo + +
+ )} +
+ ) +} + +export default ProfilePhotoSelector \ No newline at end of file diff --git a/frontend/resume-builder/src/components/Inputs/TitleInput.jsx b/frontend/resume-builder/src/components/Inputs/TitleInput.jsx new file mode 100644 index 0000000..1822db2 --- /dev/null +++ b/frontend/resume-builder/src/components/Inputs/TitleInput.jsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import { LuCheck, LuPencil } from "react-icons/lu"; + +const TitleInput = ({ title, setTitle }) => { + const [showInput, setShowInput] = useState(false); + + return
+ {showInput ? ( + <> + setTitle(target.value)} + /> + + + + ) : ( + <> +

{title}

+ + + )} +
+}; + +export default TitleInput; diff --git a/frontend/resume-builder/src/components/Modal.jsx b/frontend/resume-builder/src/components/Modal.jsx new file mode 100644 index 0000000..fae2b63 --- /dev/null +++ b/frontend/resume-builder/src/components/Modal.jsx @@ -0,0 +1,71 @@ +import React from "react"; + +const Modal = ({ + children, + isOpen, + onClose, + title, + hideHeader, + showActionBtn, + actionBtnIcon = null, + actionBtnText, + onActionClick, +}) => { + if (!isOpen) return null; + + return ( +
+ {/* Modal Content */} +
+ {/* Modal Header */} + {!hideHeader && ( +
+

{title}

+ + {showActionBtn && ( + + )} +
+ )} + + + + {/* Modal Body (Scrollable) */} +
+ {children} +
+
+
+ ); +}; + +export default Modal; diff --git a/frontend/resume-builder/src/components/Progress.jsx b/frontend/resume-builder/src/components/Progress.jsx new file mode 100644 index 0000000..7b31f1e --- /dev/null +++ b/frontend/resume-builder/src/components/Progress.jsx @@ -0,0 +1,24 @@ +import React from "react"; + +const Progress = ({ progress = 0, total = 5, color, bgColor }) => { + return ( +
+ {[...Array(total)].map((_, index) => ( +
+ ))} +
+ ); +}; + +export default Progress; diff --git a/frontend/resume-builder/src/components/ResumeSections/ActionLink.jsx b/frontend/resume-builder/src/components/ResumeSections/ActionLink.jsx new file mode 100644 index 0000000..2eb7565 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/ActionLink.jsx @@ -0,0 +1,18 @@ +import React from 'react' + +const ActionLink = ({icon, link, bgColor}) => { + return ( +
+
+ {icon} +
+ +

{link}

+
+ ) +} + +export default ActionLink \ No newline at end of file diff --git a/frontend/resume-builder/src/components/ResumeSections/CertificationInfo.jsx b/frontend/resume-builder/src/components/ResumeSections/CertificationInfo.jsx new file mode 100644 index 0000000..4baa240 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/CertificationInfo.jsx @@ -0,0 +1,22 @@ +import React from "react"; + +const CertificationInfo = ({ title, issuer, year, bgColor }) => { + return
+

{title}

+ +
+ {year && ( +
+ {year} +
+ )} + +

{issuer}

+
+
+}; + +export default CertificationInfo; diff --git a/frontend/resume-builder/src/components/ResumeSections/ContactInfo.jsx b/frontend/resume-builder/src/components/ResumeSections/ContactInfo.jsx new file mode 100644 index 0000000..b97d015 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/ContactInfo.jsx @@ -0,0 +1,16 @@ +import React from "react"; + +const ContactInfo = ({ icon, iconBG, value }) => { + return
+
+ {icon} +
+ +

{value}

+
+}; + +export default ContactInfo; diff --git a/frontend/resume-builder/src/components/ResumeSections/EducationInfo.jsx b/frontend/resume-builder/src/components/ResumeSections/EducationInfo.jsx new file mode 100644 index 0000000..5fc9ce9 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/EducationInfo.jsx @@ -0,0 +1,13 @@ +import React from "react"; + +const EducationInfo = ({ degree, institution, duration }) => { + return
+

{degree}

+

{institution}

+

+ {duration} +

+
+}; + +export default EducationInfo; diff --git a/frontend/resume-builder/src/components/ResumeSections/LanguageSection.jsx b/frontend/resume-builder/src/components/ResumeSections/LanguageSection.jsx new file mode 100644 index 0000000..9e438d8 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/LanguageSection.jsx @@ -0,0 +1,33 @@ +import React from "react"; +import Progress from "../Progress"; + +const LanguageInfo = ({ language, progress, accentColor, bgColor }) => { + return ( +
+

{language}

+ {progress > 0 && ( + + )} +
+ ); +}; + +const LanguageSection = ({ languages, accentColor, bgColor }) => { + return
+ {languages?.map((language, index) => ( + + ))} +
+}; + +export default LanguageSection; diff --git a/frontend/resume-builder/src/components/ResumeSections/ProjectInfo.jsx b/frontend/resume-builder/src/components/ResumeSections/ProjectInfo.jsx new file mode 100644 index 0000000..2b7a9e7 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/ProjectInfo.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { LuGithub, LuExternalLink } from "react-icons/lu"; +import ActionLink from "./ActionLink"; + +const ProjectInfo = ({ + title, + description, + githubLink, + liveDemoUrl, + bgColor, + isPreview +}) => { + return
+

+ {title} +

+

{description}

+ +
+ {githubLink && } link={githubLink} bgColor={bgColor} />} + + {liveDemoUrl && } link={liveDemoUrl} bgColor={bgColor} />} +
+
+}; + +export default ProjectInfo; diff --git a/frontend/resume-builder/src/components/ResumeSections/RatingInput.jsx b/frontend/resume-builder/src/components/ResumeSections/RatingInput.jsx new file mode 100644 index 0000000..257bb0c --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/RatingInput.jsx @@ -0,0 +1,37 @@ +import React from "react"; + +const RatingInput = ({ + value = 0, + total = 5, + onChange = () => {}, + color = "#9125E6", + bgColor = "#E9D4FF", +}) => { + // Convert 0–100 to 0–5 scale + const displayValue = Math.round((value / 100) * total); + + const handleClick = (index) => { + // Convert 0–5 scale back to 0–100 for DB + const newValue = Math.round(((index + 1) / total) * 100); + onChange(newValue); + }; + + return
+ {[...Array(total)].map((_, index) => { + const isActive = index < displayValue; + + return ( +
handleClick(index)} + className="w-4 h-4 rounded transition-all" + style={{ + backgroundColor: isActive ? color : bgColor, + }} + >
+ ); + })} +
+}; + +export default RatingInput; diff --git a/frontend/resume-builder/src/components/ResumeSections/SkillSection.jsx b/frontend/resume-builder/src/components/ResumeSections/SkillSection.jsx new file mode 100644 index 0000000..e90e579 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/SkillSection.jsx @@ -0,0 +1,33 @@ +import React from "react"; +import Progress from "../Progress"; + +const SkillInfo = ({ skill, progress, accentColor, bgColor }) => { + return ( +
+

{skill}

+ {progress > 0 && ( + + )} +
+ ); +}; + +const SkillSection = ({ skills, accentColor, bgColor }) => { + return
+ {skills?.map((skill, index) => ( + + ))} +
+}; + +export default SkillSection; diff --git a/frontend/resume-builder/src/components/ResumeSections/WorkExperience.jsx b/frontend/resume-builder/src/components/ResumeSections/WorkExperience.jsx new file mode 100644 index 0000000..b446f7e --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeSections/WorkExperience.jsx @@ -0,0 +1,30 @@ +import React from "react"; + +const WorkExperience = ({ + company, + role, + duration, + durationColor, + description, +}) => { + return
+
+
+

+ {company} +

+

{role}

+
+ +

+ {duration} +

+
+ +

+ {description} +

+
+}; + +export default WorkExperience; diff --git a/frontend/resume-builder/src/components/ResumeTemplates/RenderResume.jsx b/frontend/resume-builder/src/components/ResumeTemplates/RenderResume.jsx new file mode 100644 index 0000000..b6e0ea6 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeTemplates/RenderResume.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import TemplateOne from "./TemplateOne"; +import TemplateTwo from "./TemplateTwo"; +import TemplateThree from "./TemplateThree"; + +const RenderResume = ({ + templateId, + resumeData, + colorPalette, + containerWidth, +}) => { + switch (templateId) { + case "01": + return ( + + ); + case "02": + return ( + + ); + case "03": + return ( + + ); + default: + return ( + + ); + } +}; + +export default RenderResume; diff --git a/frontend/resume-builder/src/components/ResumeTemplates/TemplateOne.jsx b/frontend/resume-builder/src/components/ResumeTemplates/TemplateOne.jsx new file mode 100644 index 0000000..a3107c5 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeTemplates/TemplateOne.jsx @@ -0,0 +1,254 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + LuMapPinHouse, + LuMail, + LuPhone, + LuRss, + LuGithub, + LuUser, +} from "react-icons/lu"; +import { RiLinkedinLine } from "react-icons/ri"; +import ContactInfo from "../ResumeSections/ContactInfo"; +import EducationInfo from "../ResumeSections/EducationInfo"; +import { formatYearMonth } from "../../utils/helper"; +import LanguageSection from "../ResumeSections/LanguageSection"; +import WorkExperience from "../ResumeSections/WorkExperience"; +import ProjectInfo from "../ResumeSections/ProjectInfo"; +import SkillSection from "../ResumeSections/SkillSection"; +import CertificationInfo from "../ResumeSections/CertificationInfo"; + +const DEFAULT_THEME = ["#EBFDFF", "#A1F4FD", "#CEFAFE", "#00B8DB", "#4A5565"]; + +const Title = ({ text, color }) => { + return ( +
+ +

{text}

+
+ ); +}; + +const TemplateOne = ({ resumeData, colorPalette, containerWidth }) => { + const themeColors = colorPalette?.length > 0 ? colorPalette : DEFAULT_THEME; + + const resumeRef = useRef(null); + const [baseWidth, setBaseWidth] = useState(800); // Default value + const [scale, setScale] = useState(1); + + useEffect(() => { + // Calculate the scale factor based on the container width + const actualBaseWidth = resumeRef.current.offsetWidth; + setBaseWidth(actualBaseWidth); // Get the actual base width + setScale(containerWidth / baseWidth); + }, [containerWidth]); + + return ( +
0 ? `scale(${scale})` : "none", + transformOrigin: "top left", + width: containerWidth > 0 ? `${baseWidth}px` : "auto", // Keep the original size so scaling works correctly + height: "auto", + }} + > +
+
+
+
+ {resumeData.profileInfo.profilePreviewUrl ? ( + + ) : ( +
+ +
+ )} +
+ +

+ {resumeData.profileInfo.fullName} +

+

+ {resumeData.profileInfo.designation} +

+
+ +
+
+ } + iconBG={themeColors[2]} + value={resumeData.contactInfo.location} + /> + + } + iconBG={themeColors[2]} + value={resumeData.contactInfo.email} + /> + + } + iconBG={themeColors[2]} + value={resumeData.contactInfo.phone} + /> + + {resumeData.contactInfo.linkedin && ( + } + iconBG={themeColors[2]} + value={resumeData.contactInfo.linkedin} + /> + )} + + {resumeData.contactInfo.github && ( + } + iconBG={themeColors[2]} + value={resumeData.contactInfo.github} + /> + )} + + } + iconBG={themeColors[2]} + value={resumeData.contactInfo.website} + /> +
+ +
+ + + {resumeData.education.map((data, index) => ( + <EducationInfo + key={`education_${index}`} + degree={data.degree} + institution={data.institution} + duration={`${formatYearMonth( + data.startDate + )} - ${formatYearMonth(data.endDate)}`} + /> + ))} + </div> + + <div className="mt-5"> + <Title text="Languages" color={themeColors[1]} /> + + <LanguageSection + languages={resumeData.languages} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + </div> + </div> + + <div className="col-span-8 pt-10 mr-10 pb-5"> + <div> + <Title text="Professional Summary" color={themeColors[1]} /> + <p className="text-sm font-medium"> + {resumeData.profileInfo.summary} + </p> + </div> + + <div className="mt-4"> + <Title text="Work Experiance" color={themeColors[1]} /> + + {resumeData.workExperience.map((data, index) => ( + <WorkExperience + key={`work_${index}`} + company={data.company} + role={data.role} + duration={`${formatYearMonth( + data.startDate + )} - ${formatYearMonth(data.endDate)}`} + durationColor={themeColors[4]} + description={data.description} + /> + ))} + </div> + + <div className="mt-4"> + <Title text="Projects" color={themeColors[1]} /> + + {resumeData.projects.map((project, index) => ( + <ProjectInfo + key={`project_${index}`} + title={project.title} + description={project.description} + githubLink={project.github} + liveDemoUrl={project.liveDemo} + bgColor={themeColors[2]} + /> + ))} + </div> + + <div className="mt-4"> + <Title text="Skills" color={themeColors[1]} /> + + <SkillSection + skills={resumeData.skills} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + + <div className="mt-4"> + <Title text="Certifications" color={themeColors[1]} /> + + <div className="grid grid-cols-2 gap-2"> + {resumeData.certifications.map((data, index) => ( + <CertificationInfo + key={`cert_${index}`} + title={data.title} + issuer={data.issuer} + year={data.year} + bgColor={themeColors[2]} + /> + ))} + </div> + </div> + + {resumeData.interests.length > 0 && resumeData.interests[0] != "" && ( + <div className="mt-4"> + <Title text="Interests" color={themeColors[1]} /> + + <div className="flex items-center flex-wrap gap-3 mt-4"> + {resumeData.interests.map((interest, index) => { + if (!interest) return null; + return ( + <div + key={`interest_${index}`} + className="text-[10px] font-medium py-1 px-3 rounded-lg" + style={{ backgroundColor: themeColors[2] }} + > + {interest} + </div> + ); + })} + </div> + </div> + )} + </div> + </div> + </div> + ); +}; + +export default TemplateOne; diff --git a/frontend/resume-builder/src/components/ResumeTemplates/TemplateThree.jsx b/frontend/resume-builder/src/components/ResumeTemplates/TemplateThree.jsx new file mode 100644 index 0000000..c469c57 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeTemplates/TemplateThree.jsx @@ -0,0 +1,264 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + LuMapPinHouse, + LuMail, + LuPhone, + LuRss, + LuGithub, + LuUser, +} from "react-icons/lu"; +import { RiLinkedinLine } from "react-icons/ri"; +import ContactInfo from "../ResumeSections/ContactInfo"; +import EducationInfo from "../ResumeSections/EducationInfo"; +import { formatYearMonth } from "../../utils/helper"; +import LanguageSection from "../ResumeSections/LanguageSection"; +import WorkExperience from "../ResumeSections/WorkExperience"; +import ProjectInfo from "../ResumeSections/ProjectInfo"; +import SkillSection from "../ResumeSections/SkillSection"; +import CertificationInfo from "../ResumeSections/CertificationInfo"; + +const DEFAULT_THEME = ["#EBFDFF", "#A1F4FD", "#CEFAFE", "#00B8DB", "#4A5565"]; + +const Title = ({ text, color }) => { + return ( + <div className="relative w-fit mb-2.5"> + <span + className="absolute bottom-0 left-0 w-full h-2" + style={{ backgroundColor: color }} + ></span> + <h2 className={`relative text-sm font-bold`}>{text}</h2> + </div> + ); +}; + +const TemplateThree = ({ resumeData, colorPalette, containerWidth }) => { + const themeColors = colorPalette?.length > 0 ? colorPalette : DEFAULT_THEME; + + const resumeRef = useRef(null); + const [baseWidth, setBaseWidth] = useState(800); // Default value + const [scale, setScale] = useState(1); + + useEffect(() => { + // Calculate the scale factor based on the container width + const actualBaseWidth = resumeRef.current.offsetWidth; + setBaseWidth(actualBaseWidth); // Get the actual base width + setScale(containerWidth / baseWidth); + }, [containerWidth]); + + return ( + <div + ref={resumeRef} + className="p-3 bg-white" + style={{ + transform: containerWidth > 0 ? `scale(${scale})` : "none", + transformOrigin: "top left", + width: containerWidth > 0 ? `${baseWidth}px` : "auto", // Keep the original size so scaling works correctly + height: "auto", + }} + > + + <div className="flex items-start gap-5 px-2 mb-5"> + <div + className="w-[100px] h-[100px] max-w-[105px] max-h-[105px] rounded-2xl flex items-center justify-center" + style={{ backgroundColor: themeColors[1] }} + > + {resumeData.profileInfo.profilePreviewUrl ? ( + <img + src={resumeData.profileInfo.profilePreviewUrl} + className="w-[90px] h-[90px] rounded-2xl" + /> + ) : ( + <div + className="w-[90px] h-[90px] flex items-center justify-center text-5xl rounded-full" + style={{ color: themeColors[4] }} + > + <LuUser /> + </div> + )} + </div> + + <div> + <div className="grid grid-cols-12 items-center"> + <div className="col-span-8"> + <h2 className="text-2xl font-bold"> + {resumeData.profileInfo.fullName} + </h2> + <p className="text-[15px] font-semibold mb-2"> + {resumeData.profileInfo.designation} + </p> + + <ContactInfo + icon={<LuMapPinHouse />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.location} + /> + </div> + + <div className="col-span-4 flex flex-col gap-5 mt-2"> + <ContactInfo + icon={<LuMail />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.email} + /> + + <ContactInfo + icon={<LuPhone />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.phone} + /> + </div> + </div> + </div> + </div> + + <div className="grid grid-cols-12 gap-8"> + <div + className="col-span-4 py-10" + style={{ backgroundColor: themeColors[0] }} + > + + <div className="my-6 mx-6"> + <div className="flex flex-col gap-4"> + {resumeData.contactInfo.linkedin && ( + <ContactInfo + icon={<RiLinkedinLine />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.linkedin} + /> + )} + + {resumeData.contactInfo.github && ( + <ContactInfo + icon={<LuGithub />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.github} + /> + )} + + <ContactInfo + icon={<LuRss />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.website} + /> + </div> + + <div className="mt-5"> + <Title text="Education" color={themeColors[1]} /> + + {resumeData.education.map((data, index) => ( + <EducationInfo + key={`education_${index}`} + degree={data.degree} + institution={data.institution} + duration={`${formatYearMonth( + data.startDate + )} - ${formatYearMonth(data.endDate)}`} + /> + ))} + </div> + + <div className="mt-5"> + <Title text="Languages" color={themeColors[1]} /> + + <LanguageSection + languages={resumeData.languages} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + </div> + </div> + + <div className="col-span-8 pt-10 mr-10 pb-5"> + <div> + <Title text="Professional Summary" color={themeColors[1]} /> + <p className="text-sm font-medium"> + {resumeData.profileInfo.summary} + </p> + </div> + + <div className="mt-4"> + <Title text="Work Experiance" color={themeColors[1]} /> + + {resumeData.workExperience.map((data, index) => ( + <WorkExperience + key={`work_${index}`} + company={data.company} + role={data.role} + duration={`${formatYearMonth( + data.startDate + )} - ${formatYearMonth(data.endDate)}`} + durationColor={themeColors[4]} + description={data.description} + /> + ))} + </div> + + <div className="mt-4"> + <Title text="Projects" color={themeColors[1]} /> + + {resumeData.projects.map((project, index) => ( + <ProjectInfo + key={`project_${index}`} + title={project.title} + description={project.description} + githubLink={project.github} + liveDemoUrl={project.liveDemo} + bgColor={themeColors[2]} + /> + ))} + </div> + + <div className="mt-4"> + <Title text="Skills" color={themeColors[1]} /> + + <SkillSection + skills={resumeData.skills} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + + <div className="mt-4"> + <Title text="Certifications" color={themeColors[1]} /> + + <div className="grid grid-cols-2 gap-2"> + {resumeData.certifications.map((data, index) => ( + <CertificationInfo + key={`cert_${index}`} + title={data.title} + issuer={data.issuer} + year={data.year} + bgColor={themeColors[2]} + /> + ))} + </div> + </div> + + {resumeData.interests.length > 0 && resumeData.interests[0] != "" && ( + <div className="mt-4"> + <Title text="Interests" color={themeColors[1]} /> + + <div className="flex items-center flex-wrap gap-3 mt-4"> + {resumeData.interests.map((interest, index) => { + if (!interest) return null; + return ( + <div + key={`interest_${index}`} + className="text-[10px] font-medium py-1 px-3 rounded-lg" + style={{ backgroundColor: themeColors[2] }} + > + {interest} + </div> + ); + })} + </div> + </div> + )} + </div> + </div> + </div> + ); +}; + +export default TemplateThree; diff --git a/frontend/resume-builder/src/components/ResumeTemplates/TemplateTwo.jsx b/frontend/resume-builder/src/components/ResumeTemplates/TemplateTwo.jsx new file mode 100644 index 0000000..e26ce01 --- /dev/null +++ b/frontend/resume-builder/src/components/ResumeTemplates/TemplateTwo.jsx @@ -0,0 +1,254 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + LuMapPinHouse, + LuMail, + LuPhone, + LuRss, + LuGithub, + LuUser, +} from "react-icons/lu"; +import { RiLinkedinLine } from "react-icons/ri"; +import ContactInfo from "../ResumeSections/ContactInfo"; +import EducationInfo from "../ResumeSections/EducationInfo"; +import { formatYearMonth } from "../../utils/helper"; +import LanguageSection from "../ResumeSections/LanguageSection"; +import WorkExperience from "../ResumeSections/WorkExperience"; +import ProjectInfo from "../ResumeSections/ProjectInfo"; +import SkillSection from "../ResumeSections/SkillSection"; +import CertificationInfo from "../ResumeSections/CertificationInfo"; + +const DEFAULT_THEME = ["#EBFDFF", "#A1F4FD", "#CEFAFE", "#00B8DB", "#4A5565"]; + +const Title = ({ text, color }) => { + return ( + <div className="relative w-fit mb-2.5"> + <span + className="absolute bottom-0 left-0 w-full h-2" + style={{ backgroundColor: color }} + ></span> + <h2 className={`relative text-sm font-bold`}>{text}</h2> + </div> + ); +}; + +const TemplateTwo = ({ resumeData, colorPalette, containerWidth }) => { + const themeColors = colorPalette?.length > 0 ? colorPalette : DEFAULT_THEME; + + const resumeRef = useRef(null); + const [baseWidth, setBaseWidth] = useState(800); // Default value + const [scale, setScale] = useState(1); + + useEffect(() => { + // Calculate the scale factor based on the container width + const actualBaseWidth = resumeRef.current.offsetWidth; + setBaseWidth(actualBaseWidth); // Get the actual base width + setScale(containerWidth / baseWidth); + }, [containerWidth]); + + return ( + <div + ref={resumeRef} + className="p-3 bg-white" + style={{ + transform: containerWidth > 0 ? `scale(${scale})` : "none", + transformOrigin: "top left", + width: containerWidth > 0 ? `${baseWidth}px` : "auto", // Keep the original size so scaling works correctly + height: "auto", + }} + > + <div className="px-10 pt-10 pb-5"> + <div className="flex items-start gap-5 mb-5"> + <div + className="w-[140px] h-[140px] max-w-[140px] max-h-[140px] rounded-2xl flex items-center justify-center" + style={{ backgroundColor: themeColors[1] }} + > + {resumeData.profileInfo.profilePreviewUrl ? ( + <img + src={resumeData.profileInfo.profilePreviewUrl} + className="w-[140px] h-[140px] rounded-2xl" + /> + ) : ( + <div + className="w-[140px] h-[140px] flex items-center justify-center text-5xl rounded-full" + style={{ color: themeColors[4] }} + > + <LuUser /> + </div> + )} + </div> + + + <div> + <div className="grid grid-cols-12 gap-2 items-center"> + <div className="col-span-6"> + <h2 className="text-2xl font-bold"> + {resumeData.profileInfo.fullName} + </h2> + <p className="text-[15px] font-semibold mb-2"> + {resumeData.profileInfo.designation} + </p> + + <ContactInfo + icon={<LuMapPinHouse />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.location} + /> + </div> + + <div className="col-span-6 flex flex-col gap-5 mt-2"> + <ContactInfo + icon={<LuMail />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.email} + /> + + <ContactInfo + icon={<LuPhone />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.phone} + /> + </div> + + <div className="col-span-6"> + {resumeData.contactInfo.linkedin && ( + <ContactInfo + icon={<RiLinkedinLine />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.linkedin} + /> + )} + </div> + + <div className="col-span-6"> + <ContactInfo + icon={<LuRss />} + iconBG={themeColors[2]} + value={resumeData.contactInfo.website} + /> + </div> + </div> + </div> + </div> + </div> + + <div className="mx-10 pb-5"> + <div> + <Title text="Professional Summary" color={themeColors[1]} /> + <p className="text-sm font-medium"> + {resumeData.profileInfo.summary} + </p> + </div> + + <div className="mt-4"> + <Title text="Work Experiance" color={themeColors[1]} /> + + {resumeData.workExperience.map((data, index) => ( + <WorkExperience + key={`work_${index}`} + company={data.company} + role={data.role} + duration={`${formatYearMonth(data.startDate)} - ${formatYearMonth( + data.endDate + )}`} + durationColor={themeColors[4]} + description={data.description} + /> + ))} + </div> + + <div className="mt-4"> + <Title text="Projects" color={themeColors[1]} /> + + {resumeData.projects.map((project, index) => ( + <ProjectInfo + key={`project_${index}`} + title={project.title} + description={project.description} + githubLink={project.github} + liveDemoUrl={project.liveDemo} + bgColor={themeColors[2]} + /> + ))} + </div> + + <div className="mt-5"> + <Title text="Education" color={themeColors[1]} /> + + <div className="grid grid-cols-2 gap-3"> + {resumeData.education.map((data, index) => ( + <EducationInfo + key={`education_${index}`} + degree={data.degree} + institution={data.institution} + duration={`${formatYearMonth( + data.startDate + )} - ${formatYearMonth(data.endDate)}`} + /> + ))} + </div> + </div> + + <div className="mt-4"> + <Title text="Certifications" color={themeColors[1]} /> + + <div className="grid grid-cols-2 gap-6"> + {resumeData.certifications.map((data, index) => ( + <CertificationInfo + key={`cert_${index}`} + title={data.title} + issuer={data.issuer} + year={data.year} + bgColor={themeColors[2]} + /> + ))} + </div> + </div> + + <div className="mt-4"> + <Title text="Skills" color={themeColors[1]} /> + + <SkillSection + skills={resumeData.skills} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + + <div className="grid grid-cols-2 gap-10 mt-4"> + <div className=""> + <Title text="Languages" color={themeColors[1]} /> + + <LanguageSection + languages={resumeData.languages} + accentColor={themeColors[3]} + bgColor={themeColors[2]} + /> + </div> + + {resumeData.interests.length > 0 && resumeData.interests[0] != "" && ( + <div className=""> + <Title text="Interests" color={themeColors[1]} /> + + <div className="flex items-center flex-wrap gap-3 mt-4"> + {resumeData.interests.map((interest, index) => { + if (!interest) return null; + return ( + <div + key={`interest_${index}`} + className="text-[10px] font-medium py-1 px-3 rounded-lg" + style={{ backgroundColor: themeColors[2] }} + > + {interest} + </div> + ); + })} + </div> + </div> + )} + </div> + </div> + </div> + ); +}; + +export default TemplateTwo; diff --git a/frontend/resume-builder/src/components/StepProgress.jsx b/frontend/resume-builder/src/components/StepProgress.jsx new file mode 100644 index 0000000..5881ba8 --- /dev/null +++ b/frontend/resume-builder/src/components/StepProgress.jsx @@ -0,0 +1,14 @@ +import React from 'react' + +const StepProgress = ({progress}) => { + return ( + <div className="w-full bg-purple-50 h-1 overflow-hidden rounded-[2px]"> + <div + className="h-1 bg-linear-to-r from-purple-500/85 to-purple-700 transition-all rounded" + style={{ width: `${progress}%` }} + ></div> + </div> + ) +} + +export default StepProgress \ No newline at end of file diff --git a/frontend/resume-builder/src/components/Tabs.jsx b/frontend/resume-builder/src/components/Tabs.jsx new file mode 100644 index 0000000..b71c9b0 --- /dev/null +++ b/frontend/resume-builder/src/components/Tabs.jsx @@ -0,0 +1,31 @@ +import React from 'react' + +const Tabs = ({tabs, activeTab, setActiveTab}) => { + return ( + <div className="my-2"> + <div className="flex"> + {tabs.map((tab) => ( + <button + key={tab.label} + className={`relative px-3 md:px-4 py-2 text-sm font-medium ${ + activeTab === tab.label + ? "text-primary" + : "text-gray-500 hover:text-gray-700" + } cursor-pointer`} + onClick={() => setActiveTab(tab.label)} + > + <div className="flex items-center"> + <span className="text-[14px] font-semibold text-purple-700">{tab.label}</span> + + </div> + {activeTab === tab.label && ( + <div className="absolute bottom-0 left-0 w-full h-0.5 bg-linear-to-r from-purple-500/85 to-purple-700"></div> + )} + </button> + ))} + </div> + </div> + ) +} + +export default Tabs \ No newline at end of file diff --git a/frontend/resume-builder/src/components/layouts/DashboardLayout.jsx b/frontend/resume-builder/src/components/layouts/DashboardLayout.jsx new file mode 100644 index 0000000..ab5eda0 --- /dev/null +++ b/frontend/resume-builder/src/components/layouts/DashboardLayout.jsx @@ -0,0 +1,16 @@ +import React, { useContext } from "react"; +import { UserContext } from "../../context/userContext"; +import Navbar from "./Navbar"; + +const DashboardLayout = ({ activeMenu, children }) => { + const { user } = useContext(UserContext); + return ( + <div> + <Navbar activeMenu={activeMenu} /> + + {user && <div className="container mx-auto pt-4 pb-4">{children}</div>} + </div> + ); +}; + +export default DashboardLayout; diff --git a/frontend/resume-builder/src/components/layouts/Navbar.jsx b/frontend/resume-builder/src/components/layouts/Navbar.jsx new file mode 100644 index 0000000..c162b50 --- /dev/null +++ b/frontend/resume-builder/src/components/layouts/Navbar.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import ProfileInfoCard from "../Cards/ProfileInfoCard"; +import { Link } from "react-router-dom"; + +const Navbar = () => { + return <div className="h-16 bg-white border boredr-b border-gray-200/50 backdrop-blur-[2px] py-2.5 px-4 md:px-0 sticky top-0 z-30"> + <div className="container mx-auto flex items-center justify-between gap-5"> + <Link to='/dashboard'> + <h2 className="text-lg md:text-xl font-medium text-black leading-5"> + Resume Builder + </h2> + </Link> + + <ProfileInfoCard /> + </div> + </div> +}; + +export default Navbar; diff --git a/frontend/resume-builder/src/context/userContext.jsx b/frontend/resume-builder/src/context/userContext.jsx new file mode 100644 index 0000000..b29ed93 --- /dev/null +++ b/frontend/resume-builder/src/context/userContext.jsx @@ -0,0 +1,53 @@ +import React, { createContext, useState, useEffect } from "react"; +import axiosInstance from "../utils/axiosInstance"; +import { API_PATHS } from "../utils/apiPaths"; + +export const UserContext = createContext(); + +const UserProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); // New state to track loading + + useEffect(() => { + if (user) return; + + const accessToken = localStorage.getItem("token"); + if (!accessToken) { + setLoading(false); + return; + } + + const fetchUser = async () => { + try { + const response = await axiosInstance.get(API_PATHS.AUTH.GET_PROFILE); + setUser(response.data); + } catch (error) { + console.error("User not authenticated", error); + clearUser(); + } finally { + setLoading(false); + } + }; + + fetchUser(); + }, []); + + const updateUser = (userData) => { + setUser(userData); + localStorage.setItem("token", userData.token); // Save token + setLoading(false); + }; + + const clearUser = () => { + setUser(null); + localStorage.removeItem("token"); + }; + + return ( + <UserContext.Provider value={{ user, loading, updateUser, clearUser }}> + {children} + </UserContext.Provider> + ); +}; + +export default UserProvider; diff --git a/frontend/resume-builder/src/index.css b/frontend/resume-builder/src/index.css new file mode 100644 index 0000000..809ade0 --- /dev/null +++ b/frontend/resume-builder/src/index.css @@ -0,0 +1,69 @@ +@import url('https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap'); +@import "tailwindcss"; + +@theme { + --font-display: "Urbanist", sans-serif; + --breakpoint-3xl: 1920px; + --color-primary: #9328E7; +} + +@layer base { + html { + font-family: var(--font-display); + } + + body { + background-color: #fcfbfc; + overflow-x: hidden; + } +} + +/* Custom scrollbar styling */ +.custom-scrollbar::-webkit-scrollbar { + width: 4px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background-color: rgba(100, 100, 100, 0.4); + border-radius: 10px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} + +@layer utilities { + /* Animate text with a shine effect */ + @keyframes text-shine { + 0% { + background-position: 0% 50%; + } + 100% { + background-position: 100% 50%; + } + } + + .animate-text-shine { + animation: text-shine 3s ease-in-out infinite alternate; + } +} + +.input-box { + @apply w-full flex justify-between gap-3 text-sm text-black bg-gray-50/50 rounded px-4 py-3 mb-4 mt-3 border border-gray-100 outline-none focus-within:border-purple-300; +} + +.btn-primary { + @apply w-full text-sm font-medium text-white bg-black shadow-lg shadow-purple-600/5 p-[10px] rounded-md my-1 hover:bg-purple-600/15 hover:text-black cursor-pointer; +} + +.btn-small { + @apply flex items-center gap-2 text-[13px] font-semibold text-white bg-linear-to-r from-purple-500/85 to-purple-700 px-5 py-1.5 rounded cursor-pointer; +} + +.btn-small-light { + @apply flex items-center gap-2 text-[12px] font-semibold text-purple-800 bg-purple-600/15 border border-purple-50 hover:border-purple-400 px-3 py-1.5 rounded cursor-pointer; +} + +.form-input { + @apply w-full text-sm text-black outline-none bg-white border border-slate-100 px-2.5 py-3 rounded-md mt-2 placeholder:text-gray-500 focus-within:border-purple-300; +} diff --git a/frontend/resume-builder/src/main.jsx b/frontend/resume-builder/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/frontend/resume-builder/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + <StrictMode> + <App /> + </StrictMode>, +) diff --git a/frontend/resume-builder/src/pages/Auth/Login.jsx b/frontend/resume-builder/src/pages/Auth/Login.jsx new file mode 100644 index 0000000..afbd76d --- /dev/null +++ b/frontend/resume-builder/src/pages/Auth/Login.jsx @@ -0,0 +1,102 @@ +import React, { useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Input from "../../components/Inputs/Input"; +import { validateEmail } from "../../utils/helper"; +import { UserContext } from "../../context/userContext"; +import axiosInstance from "../../utils/axiosInstance"; +import { API_PATHS } from "../../utils/apiPaths"; + +const Login = ({ setCurrentPage }) => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + + const { updateUser } = useContext(UserContext); + const navigate = useNavigate(); + + // Handle Login Form Submit + const handleLogin = async (e) => { + e.preventDefault(); + + if (!validateEmail(email)) { + setError("Please enter a valid email address."); + return; + } + + if (!password) { + setError("Please enter the password"); + return; + } + + setError(""); + + //Login API Call + try { + const response = await axiosInstance.post(API_PATHS.AUTH.LOGIN, { + email, + password, + }); + + const { token } = response.data; + + if (token) { + localStorage.setItem("token", token); + updateUser(response.data); + navigate("/dashboard"); + } + } catch (error) { + if (error.response && error.response.data.message) { + setError(error.response.data.message); + } else { + setError("Something went wrong. Please try again."); + } + } + }; + + return ( + <div className="w-[90vw] md:w-[33vw] p-7 flex flex-col justify-center"> + <h3 className="text-lg font-semibold text-black">Welcome Back</h3> + <p className="text-xs text-slate-700 mt-[5px] mb-6"> + Please enter your details to log in + </p> + + <form onSubmit={handleLogin}> + <Input + value={email} + onChange={({ target }) => setEmail(target.value)} + label="Email Address" + placeholder="john@example.com" + type="text" + /> + + <Input + value={password} + onChange={({ target }) => setPassword(target.value)} + label="Password" + placeholder="Min 8 Characters" + type="password" + /> + + {error && <p className="text-red-500 text-xs pb-2.5">{error}</p>} + + <button type="submit" className="btn-primary"> + LOGIN + </button> + + <p className="text-[13px] text-slate-800 mt-3"> + Don’t have an account?{" "} + <button + className="font-medium text-primary underline cursor-pointer" + onClick={() => { + setCurrentPage("signup"); + }} + > + SignUp + </button> + </p> + </form> + </div> + ); +}; + +export default Login; diff --git a/frontend/resume-builder/src/pages/Auth/SignUp.jsx b/frontend/resume-builder/src/pages/Auth/SignUp.jsx new file mode 100644 index 0000000..ba17c94 --- /dev/null +++ b/frontend/resume-builder/src/pages/Auth/SignUp.jsx @@ -0,0 +1,135 @@ +import React, { useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Input from "../../components/Inputs/Input"; +import { validateEmail } from "../../utils/helper"; +import ProfilePhotoSelector from "../../components/Inputs/ProfilePhotoSelector"; +import axiosInstance from "../../utils/axiosInstance"; +import { API_PATHS } from "../../utils/apiPaths"; +import { UserContext } from "../../context/userContext"; +import uploadImage from "../../utils/uploadImage"; + +const SignUp = ({setCurrentPage}) => { + const [profilePic, setProfilePic] = useState(null); + const [fullName, setFullName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const [error, setError] = useState(null); + + const { updateUser } = useContext(UserContext); + const navigate = useNavigate(); + + // Handle SignUp Form Submit + const handleSignUp = async (e) => { + e.preventDefault(); + + let profileImageUrl = ""; + + if (!fullName) { + setError("Please enter full name."); + return; + } + + if (!validateEmail(email)) { + setError("Please enter a valid email address."); + return; + } + + if (!password) { + setError("Please enter the password"); + return; + } + + setError(""); + + //SignUp API Call + try { + // Upload image if present + if (profilePic) { + const imgUploadRes = await uploadImage(profilePic); + profileImageUrl = imgUploadRes.imageUrl || ""; + } + + const response = await axiosInstance.post(API_PATHS.AUTH.REGISTER, { + name: fullName, + email, + password, + profileImageUrl, + }); + + const { token } = response.data; + + if (token) { + localStorage.setItem("token", token); + updateUser(response.data); + navigate("/dashboard"); + } + } catch (error) { + if (error.response && error.response.data.message) { + setError(error.response.data.message); + } else { + setError("Something went wrong. Please try again."); + } + } + }; + + return ( + <div className="w-[90vw] md:w-[33vw] p-7 flex flex-col justify-center"> + <h3 className="text-lg font-semibold text-black">Create an Account</h3> + <p className="text-xs text-slate-700 mt-[5px] mb-6"> + Join us today by entering your details below. + </p> + + <form onSubmit={handleSignUp}> + + <ProfilePhotoSelector image={profilePic} setImage={setProfilePic} /> + + <div className="grid grid-cols-1 md:grid-cols-1 gap-2"> + <Input + value={fullName} + onChange={({ target }) => setFullName(target.value)} + label="Full Name" + placeholder="John" + type="text" + /> + + <Input + value={email} + onChange={({ target }) => setEmail(target.value)} + label="Email Address" + placeholder="john@example.com" + type="text" + /> + + <Input + value={password} + onChange={({ target }) => setPassword(target.value)} + label="Password" + placeholder="Min 8 Characters" + type="password" + /> + </div> + + {error && <p className="text-red-500 text-xs pb-2.5">{error}</p>} + + <button type="submit" className="btn-primary"> + SIGN UP + </button> + + <p className="text-[13px] text-slate-800 mt-3"> + Already an account?{" "} + <button + className="font-medium text-primary underline cursor-pointer" + onClick={() => { + setCurrentPage("login"); + }} + > + Login + </button> + </p> + </form> + </div> + ) +} + +export default SignUp \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/Home/CreateResumeForm.jsx b/frontend/resume-builder/src/pages/Home/CreateResumeForm.jsx new file mode 100644 index 0000000..6c6991e --- /dev/null +++ b/frontend/resume-builder/src/pages/Home/CreateResumeForm.jsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react' +import { useNavigate } from 'react-router-dom'; +import Input from "../../components/Inputs/Input"; +import axiosInstance from '../../utils/axiosInstance'; +import { API_PATHS } from '../../utils/apiPaths'; + +const CreateResumeForm = () => { + const [title, setTitle] = useState(""); + const [error, setError] = useState(null); + + const navigate = useNavigate(); + + // Handle Create Resume + const handleCreateResume = async (e) => { + e.preventDefault(); + + if (!title) { + setError("Please resume title"); + return; + } + + setError(""); + + //Create Resume API Call + try { + const response = await axiosInstance.post(API_PATHS.RESUME.CREATE, { + title, + }); + + if (response.data?._id) { + navigate(`/resume/${response.data?._id}`); + } + } catch (error) { + if (error.response && error.response.data.message) { + setError(error.response.data.message); + } else { + setError("Something went wrong. Please try again."); + } + } + }; + return ( + <div className="w-[90vw] md:w-[70vh] p-7 flex flex-col justify-center"> + <h3 className="text-lg font-semibold text-black">Create New Resume</h3> + <p className="text-xs text-slate-700 mt-[5px] mb-3"> + Give your resume a title to get started. You can edit all details later. + </p> + + <form onSubmit={handleCreateResume}> + <Input + value={title} + onChange={({ target }) => setTitle(target.value)} + label="Resume Title" + placeholder="Eg: Mike's Resume" + type="text" + /> + + {error && <p className="text-red-500 text-xs pb-2.5">{error}</p>} + + <button type="submit" className="btn-primary"> + Create Resume + </button> + </form> + </div> + ) +} + +export default CreateResumeForm \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/Home/Dashboard.jsx b/frontend/resume-builder/src/pages/Home/Dashboard.jsx new file mode 100644 index 0000000..1e39a28 --- /dev/null +++ b/frontend/resume-builder/src/pages/Home/Dashboard.jsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axiosInstance from "../../utils/axiosInstance"; +import { API_PATHS } from "../../utils/apiPaths"; +import DashboardLayout from "../../components/layouts/DashboardLayout"; +import {LuCirclePlus} from 'react-icons/lu' +import moment from 'moment' +import ResumeSummaryCard from "../../components/Cards/ResumeSummaryCard"; +import CreateResumeForm from "./CreateResumeForm"; +import Modal from "../../components/Modal"; + +const Dashboard = () => { + const navigate = useNavigate(); + + const [openCreateModal, setOpenCreateModal] = useState(false); + const [allResumes, setAllResumes] = useState(null); + + const fetchAllResumes = async () => { + try { + const response = await axiosInstance.get(API_PATHS.RESUME.GET_ALL); + setAllResumes(response.data); + } catch (error) { + console.error("Error fetching resumes:", error); + } + }; + + useEffect(() => { + fetchAllResumes(); + }, []); + + return <DashboardLayout> + <div className="grid grid-cols-1 md:grid-cols-5 gap-4 md:gap-7 pt-1 pb-6 px-4 md:px-0"> + <div + className="h-[300px] flex flex-col gap-5 items-center justify-center bg-white rounded-lg border border-purple-100 hover:border-purple-300 hover:bg-purple-50/5 cursor-pointer" + onClick={() => setOpenCreateModal(true)} + > + <div className="w-12 h-12 flex items-center justify-center bg-purple-200/60 rounded-2xl"> + <LuCirclePlus className="text-xl text-purple-500" /> + </div> + + <h3 className="font-medium text-gray-800">Add New Resume</h3> + </div> + + {allResumes?.map((resume) => ( + <ResumeSummaryCard + key={resume?._id} + imgUrl={resume?.thumbnailLink || null} + title={resume.title} + lastUpdated={ + resume?.updatedAt + ? moment(resume.updatedAt).format("Do MMM YYYY") + : "" + } + onSelect={()=>navigate(`/resume/${resume?._id}`)} + /> + ))} + </div> + + <Modal + isOpen={openCreateModal} + onClose={() => { + setOpenCreateModal(false); + }} + hideHeader + > + <div> + <CreateResumeForm /> + </div> + </Modal> + </DashboardLayout>; +}; + +export default Dashboard; diff --git a/frontend/resume-builder/src/pages/LandingPage.jsx b/frontend/resume-builder/src/pages/LandingPage.jsx new file mode 100644 index 0000000..0071977 --- /dev/null +++ b/frontend/resume-builder/src/pages/LandingPage.jsx @@ -0,0 +1,130 @@ +import React, { useContext, useState } from "react"; + +import HERO_IMG from "../assets/hero-img.png"; +import { useNavigate } from "react-router-dom"; +import Login from "./Auth/Login"; +import SignUp from "./Auth/SignUp"; +import Modal from "../components/Modal"; +import { UserContext } from "../context/userContext"; +import ProfileInfoCard from "../components/Cards/ProfileInfoCard"; + +const LandingPage = () => { + const { user } = useContext(UserContext); + const navigate = useNavigate(); + + const [openAuthModal, setOpenAuthModal] = useState(false); + const [currentPage, setCurrentPage] = useState("login"); + + const handleCTA = () => { + if (!user) { + setOpenAuthModal(true); + } else { + navigate("/dashboard"); + } + }; + + return ( + <div className="w-full min-h-full bg-white"> + <div className="container mx-auto px-4 py-6"> + {/* Header */} + <header className="flex justify-between items-center mb-16"> + <div className="text-xl font-bold">Resume Builder</div> + {user ? ( + <ProfileInfoCard /> + ) : ( + <button + className="bg-purple-100 text-sm font-semibold text-black px-7 py-2.5 rounded-lg hover:bg-gray-800 hover:text-white transition-colors cursor-pointer" + onClick={() => setOpenAuthModal(true)} + > + Login / Sign Up + </button> + )} + </header> + + {/* Hero Content */} + <div className="flex flex-col md:flex-row items-center"> + <div className="w-full md:w-1/2 pr-4 mb-8 md:mb-0"> + <h1 className="text-5xl font-bold mb-6 leading-tight"> + Build Your{" "} + <span className="text-transparent bg-clip-text bg-[radial-gradient(circle,_#7182ff_0%,_#3cff52_100%)] bg-[length:200%_200%] animate-text-shine"> + Resume Effortlessly + </span> + </h1> + <p className="text-lg text-gray-700 mb-8"> + Craft a standout resume in minutes with our smart and intuitive + resume builder. + </p> + <button + className="bg-black text-sm font-semibold text-white px-8 py-3 rounded-lg hover:bg-gray-800 transition-colors cursor-pointer" + onClick={handleCTA} + > + Get Started + </button> + </div> + <div className="w-full md:w-1/2"> + <img + src={HERO_IMG} + alt="Hero Image" + className="w-full rounded-lg" + /> + </div> + </div> + + <section className="mt-5"> + <h2 className="text-2xl font-bold text-center mb-12"> + Features That Make You Shine + </h2> + <div className="grid grid-cols-1 md:grid-cols-3 gap-8"> + <div className="bg-gray-50 p-6 rounded-xl shadow-sm hover:shadow-md transition"> + <h3 className="text-lg font-semibold mb-3">Easy Editing</h3> + <p className="text-gray-600"> + Update your resume sections with live preview and instant + formatting. + </p> + </div> + + <div className="bg-gray-50 p-6 rounded-xl shadow-sm hover:shadow-md transition"> + <h3 className="text-lg font-semibold mb-3"> + Beautiful Templates + </h3> + <p className="text-gray-600"> + Choose from modern, professional templates that are easy to + customize. + </p> + </div> + + <div className="bg-gray-50 p-6 rounded-xl shadow-sm hover:shadow-md transition"> + <h3 className="text-lg font-semibold mb-3">One-Click Export</h3> + <p className="text-gray-600"> + Download your resume instantly as a high-quality PDF with one + click. + </p> + </div> + </div> + </section> + </div> + + <div className="text-sm bg-gray-50 text-secondary text-center p-5 mt-5"> + Made with ❤️... Happy Coding + </div> + + <Modal + isOpen={openAuthModal} + onClose={() => { + setOpenAuthModal(false); + setCurrentPage("login"); + }} + hideHeader + > + <div> + {currentPage === "login" && <Login setCurrentPage={setCurrentPage} />} + {currentPage === "signup" && ( + <SignUp setCurrentPage={setCurrentPage} /> + )} + </div> + </Modal> + </div> + ); +}; + +export default LandingPage; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/EditResume.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/EditResume.jsx new file mode 100644 index 0000000..e2ee42d --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/EditResume.jsx @@ -0,0 +1,718 @@ +import React, { useEffect, useRef, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + LuArrowLeft, + LuCircleAlert, + LuDownload, + LuPalette, + LuSave, + LuTrash2, +} from "react-icons/lu"; +import toast from "react-hot-toast"; +import DashboardLayout from "../../components/layouts/DashboardLayout"; +import TitleInput from "../../components/Inputs/TitleInput"; +import { useReactToPrint } from "react-to-print"; +import axiosInstance from "../../utils/axiosInstance"; +import { API_PATHS } from "../../utils/apiPaths"; +import StepProgress from "../../components/StepProgress"; +import ProfileInfoForm from "./Forms/ProfileInfoForm"; +import ContactInfoForm from "./Forms/ContactInfoForm"; +import WorkExperienceForm from "./Forms/WorkExperienceForm"; +import EducationDetailsForm from "./Forms/EducationDetailsForm"; +import SkillsInfoForm from "./Forms/SkillsInfoForm"; +import ProjectsDetailFrom from "./Forms/ProjectsDetailFrom"; +import CertificationInfoFrom from "./Forms/CertificationInfoFrom"; +import AdditionalInfoFrom from "./Forms/AdditionalInfoFrom"; +import RenderResume from "../../components/ResumeTemplates/RenderResume"; +import { captureElementAsImage, dataURLtoFile, fixTailwindColors } from "../../utils/helper"; +import ThemeSelector from "./ThemeSelector"; +import Modal from "../../components/Modal"; + +const EditResume = () => { + const { resumeId } = useParams(); + const navigate = useNavigate(); + + const resumeRef = useRef(null); + const resumeDownloadRef = useRef(null); + + const [baseWidth, setBaseWidth] = useState(800); + + const [openThemeSelector, setOpenThemeSelector] = useState(false); + + const [openPreviewModal, setOpenPreviewModal] = useState(false); + + const [currentPage, setCurrentPage] = useState("profile-info"); + const [progress, setProgress] = useState(0); + const [resumeData, setResumeData] = useState({ + title: "", + thumbnailLink: "", + profileInfo: { + profileImg: null, + profilePreviewUrl: "", + fullName: "", + designation: "", + summary: "", + }, + template: { + theme: "", + colorPalette: "", + }, + contactInfo: { + email: "", + phone: "", + location: "", + linkedin: "", + github: "", + website: "", + }, + workExperience: [ + { + company: "", + role: "", + startDate: "", // e.g. "2022-01" + endDate: "", // e.g. "2023-12" + description: "", + }, + ], + education: [ + { + degree: "", + institution: "", + startDate: "", + endDate: "", + }, + ], + skills: [ + { + name: "", + progress: 0, // percentage value (0-100) + }, + ], + projects: [ + { + title: "", + description: "", + github: "", + liveDemo: "", + }, + ], + certifications: [ + { + title: "", + issuer: "", + year: "", + }, + ], + languages: [ + { + name: "", + progress: 0, // percentage value (0-100) + }, + ], + interests: [""], + }); + const [errorMsg, setErrorMsg] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + // Validate Inputs + const validateAndNext = (e) => { + const errors = []; + + switch (currentPage) { + case "profile-info": + const { fullName, designation, summary } = resumeData.profileInfo; + if (!fullName.trim()) errors.push("Full Name is required"); + if (!designation.trim()) errors.push("Designation is required"); + if (!summary.trim()) errors.push("Summary is required"); + break; + + case "contact-info": + const { email, phone } = resumeData.contactInfo; + if (!email.trim() || !/^\S+@\S+\.\S+$/.test(email)) + errors.push("Valid email is required."); + if (!phone.trim()) + errors.push("Valid 10-digit phone number is required"); + break; + + case "work-experience": + resumeData.workExperience.forEach( + ({ company, role, startDate, endDate }, index) => { + if (!company.trim()) + errors.push(`Company is required in experience ${index + 1}`); + if (!role.trim()) + errors.push(`Role is required in experience ${index + 1}`); + if (!startDate || !endDate) + errors.push( + `Start and End dates are required in experience ${index + 1}` + ); + } + ); + break; + + case "education-info": + resumeData.education.forEach( + ({ degree, institution, startDate, endDate }, index) => { + if (!degree.trim()) + errors.push(`Degree is required in education ${index + 1}`); + if (!institution.trim()) + errors.push(`Institution is required in education ${index + 1}`); + if (!startDate || !endDate) + errors.push( + `Start and End dates are required in education ${index + 1}` + ); + } + ); + break; + + case "skills": + resumeData.skills.forEach(({ name, progress }, index) => { + if (!name.trim()) + errors.push(`Skill name is required in skill ${index + 1}`); + if (progress < 1 || progress > 100) + errors.push( + `Skill progress must be between 1 and 100 in skill ${index + 1}` + ); + }); + break; + + case "projects": + resumeData.projects.forEach(({ title, description }, index) => { + if (!title.trim()) + errors.push(`Project title is required in project ${index + 1}`); + if (!description.trim()) + errors.push( + `Project description is required in project ${index + 1}` + ); + }); + break; + + case "certifications": + resumeData.certifications.forEach(({ title, issuer }, index) => { + if (!title.trim()) + errors.push( + `Certification title is required in certification ${index + 1}` + ); + if (!issuer.trim()) + errors.push(`Issuer is required in certification ${index + 1}`); + }); + break; + + case "additionalInfo": + if ( + resumeData.languages.length === 0 || + !resumeData.languages[0].name?.trim() + ) { + errors.push("At least one language is required"); + } + + if ( + resumeData.interests.length === 0 || + !resumeData.interests[0]?.trim() + ) { + errors.push("At least one interest is required"); + } + break; + + default: + break; + } + + if (errors.length > 0) { + setErrorMsg(errors.join(", ")); + return; + } + + // Move to next step + setErrorMsg(""); + goToNextStep(); + }; + + // Function to navigate to the next page + const goToNextStep = () => { + const pages = [ + "profile-info", + "contact-info", + "work-experience", + "education-info", + "skills", + "projects", + "certifications", + "additionalInfo", + ]; + + if (currentPage === "additionalInfo") setOpenPreviewModal(true); + + const currentIndex = pages.indexOf(currentPage); + if (currentIndex !== -1 && currentIndex < pages.length - 1) { + const nextIndex = currentIndex + 1; + setCurrentPage(pages[nextIndex]); + + // Set progress as percentage + const percent = Math.round((nextIndex / (pages.length - 1)) * 100); + setProgress(percent); + window.scrollTo({ top: 0, behavior: "smooth" }); + } + }; + + // Function to navigate to the previous page + const goBack = () => { + const pages = [ + "profile-info", + "contact-info", + "work-experience", + "education-info", + "skills", + "projects", + "certifications", + "additionalInfo", + ]; + + if (currentPage === "profile-info") navigate("/dashboard"); + + const currentIndex = pages.indexOf(currentPage); + if (currentIndex > 0) { + const prevIndex = currentIndex - 1; + setCurrentPage(pages[prevIndex]); + + // Update progress + const percent = Math.round((prevIndex / (pages.length - 1)) * 100); + setProgress(percent); + window.scrollTo({ top: 0, behavior: "smooth" }); + } + }; + + const renderForm = () => { + switch (currentPage) { + case "profile-info": + return ( + <ProfileInfoForm + profileData={resumeData?.profileInfo} + updateSection={(key, value) => { + updateSection("profileInfo", key, value); + }} + onNext={validateAndNext} + /> + ); + + case "contact-info": + return ( + <ContactInfoForm + contactInfo={resumeData?.contactInfo} + updateSection={(key, value) => { + updateSection("contactInfo", key, value); + }} + /> + ); + + case "work-experience": + return ( + <WorkExperienceForm + workExperience={resumeData?.workExperience} + updateArrayItem={(index, key, value) => { + updateArrayItem("workExperience", index, key, value); + }} + addArrayItem={(newItem) => addArrayItem("workExperience", newItem)} + removeArrayItem={(index) => + removeArrayItem("workExperience", index) + } + /> + ); + + case "education-info": + return ( + <EducationDetailsForm + educationInfo={resumeData?.education} + updateArrayItem={(index, key, value) => { + updateArrayItem("education", index, key, value); + }} + addArrayItem={(newItem) => addArrayItem("education", newItem)} + removeArrayItem={(index) => removeArrayItem("education", index)} + /> + ); + + case "skills": + return ( + <SkillsInfoForm + skillsInfo={resumeData?.skills} + updateArrayItem={(index, key, value) => { + updateArrayItem("skills", index, key, value); + }} + addArrayItem={(newItem) => addArrayItem("skills", newItem)} + removeArrayItem={(index) => removeArrayItem("skills", index)} + /> + ); + + case "projects": + return ( + <ProjectsDetailFrom + projectInfo={resumeData?.projects} + updateArrayItem={(index, key, value) => { + updateArrayItem("projects", index, key, value); + }} + addArrayItem={(newItem) => addArrayItem("projects", newItem)} + removeArrayItem={(index) => removeArrayItem("projects", index)} + /> + ); + + case "certifications": + return ( + <CertificationInfoFrom + certifications={resumeData?.certifications} + updateArrayItem={(index, key, value) => { + updateArrayItem("certifications", index, key, value); + }} + addArrayItem={(newItem) => addArrayItem("certifications", newItem)} + removeArrayItem={(index) => + removeArrayItem("certifications", index) + } + /> + ); + + case "additionalInfo": + return ( + <AdditionalInfoFrom + languages={resumeData.languages} + interests={resumeData.interests} + updateArrayItem={(section, index, key, value) => + updateArrayItem(section, index, key, value) + } + addArrayItem={(section, newItem) => addArrayItem(section, newItem)} + removeArrayItem={(section, index) => + removeArrayItem(section, index) + } + /> + ); + + default: + return null; + } + }; + + // Update simple nested object (like profileInfo, contactInfo, etc.) + const updateSection = (section, key, value) => { + setResumeData((prev) => ({ + ...prev, + [section]: { + ...prev[section], + [key]: value, + }, + })); + }; + + // Update array item (like workExperience[0], skills[1], etc.) + const updateArrayItem = (section, index, key, value) => { + setResumeData((prev) => { + const updatedArray = [...prev[section]]; + + if (key === null) { + updatedArray[index] = value; // for simple strings like in `interests` + } else { + updatedArray[index] = { + ...updatedArray[index], + [key]: value, + }; + } + + return { + ...prev, + [section]: updatedArray, + }; + }); + }; + + // Add item to array + const addArrayItem = (section, newItem) => { + setResumeData((prev) => ({ + ...prev, + [section]: [...prev[section], newItem], + })); + }; + + // Remove item from array + const removeArrayItem = (section, index) => { + setResumeData((prev) => { + const updatedArray = [...prev[section]]; + updatedArray.splice(index, 1); + return { + ...prev, + [section]: updatedArray, + }; + }); + }; + + // Fetch resume info by ID + const fetchResumeDetailsById = async () => { + try { + const response = await axiosInstance.get( + API_PATHS.RESUME.GET_BY_ID(resumeId) + ); + + if (response.data && response.data.profileInfo) { + const resumeInfo = response.data; + + setResumeData((prevState) => ({ + ...prevState, + title: resumeInfo?.title || "Untitled", + template: resumeInfo?.template || prevState?.template, + profileInfo: resumeInfo?.profileInfo || prevState?.profileInfo, + contactInfo: resumeInfo?.contactInfo || prevState?.contactInfo, + workExperience: + resumeInfo?.workExperience || prevState?.workExperience, + education: resumeInfo?.education || prevState?.education, + skills: resumeInfo?.skills || prevState?.skills, + projects: resumeInfo?.projects || prevState?.projects, + certifications: + resumeInfo?.certifications || prevState?.certifications, + languages: resumeInfo?.languages || prevState?.languages, + interests: resumeInfo?.interests || prevState?.interests, + })); + } + } catch (error) { + console.error("Error fetching resumes:", error); + } + }; + + // upload thumbnail and resume profile img + const uploadResumeImages = async () => { + try { + setIsLoading(true); + + fixTailwindColors(resumeRef.current); + const imageDataUrl = await captureElementAsImage(resumeRef.current); + + // Convert base64 to File + const thumbnailFile = dataURLtoFile( + imageDataUrl, + `resume-${resumeId}.png` + ); + + const profileImageFile = resumeData?.profileInfo?.profileImg || null; + + const formData = new FormData(); + if (profileImageFile) formData.append("profileImage", profileImageFile); + if (thumbnailFile) formData.append("thumbnail", thumbnailFile); + + const uploadResponse = await axiosInstance.put( + API_PATHS.RESUME.UPLOAD_IMAGES(resumeId), + formData, + { headers: { "Content-Type": "multipart/form-data" } } + ); + + const { thumbnailLink, profilePreviewUrl } = uploadResponse.data; + + console.log("RESUME_DATA___", resumeData); + + // Call the second API to update other resume data + await updateResumeDetails(thumbnailLink, profilePreviewUrl); + + toast.success("Resume Updated Successfully!"); + navigate("/dashboard"); + } catch (error) { + console.error("Error uploading images:", error); + toast.error("Failed to upload images"); + } finally { + setIsLoading(false); + } + }; + + const updateResumeDetails = async (thumbnailLink, profilePreviewUrl) => { + try { + setIsLoading(true); + + const response = await axiosInstance.put( + API_PATHS.RESUME.UPDATE(resumeId), + { + ...resumeData, + thumbnailLink: thumbnailLink || "", + profileInfo: { + ...resumeData.profileInfo, + profilePreviewUrl: profilePreviewUrl || "", + }, + } + ); + } catch (err) { + console.error("Error capturing image:", err); + } finally { + setIsLoading(false); + } + }; + + // Delete Resume + const handleDeleteResume = async () => { + try { + setIsLoading(true); + const response = await axiosInstance.delete(API_PATHS.RESUME.DELETE(resumeId)); + toast.success('Resume Deleted Successfully') + navigate('/dashboard') + } catch (err) { + console.error("Error capturing image:", err); + } finally { + setIsLoading(false); + } + }; + + // download resume + const reactToPrintFn = useReactToPrint({ contentRef: resumeDownloadRef }); + + // Function to update baseWidth based on the resume container size + const updateBaseWidth = () => { + if (resumeRef.current) { + setBaseWidth(resumeRef.current.offsetWidth); + } + }; + + useEffect(() => { + updateBaseWidth(); + window.addEventListener("resize", updateBaseWidth); + + if (resumeId) { + fetchResumeDetailsById(); + } + + return () => { + window.removeEventListener("resize", updateBaseWidth); + }; + }, []); + + return ( + <DashboardLayout> + <div className="container mx-auto"> + <div className="flex items-center justify-between gap-5 bg-white rounded-lg border border-purple-100 py-3 px-4 mb-4"> + <TitleInput + title={resumeData.title} + setTitle={(value) => + setResumeData((prevState) => ({ + ...prevState, + title: value, + })) + } + /> + + <div className="flex items-center gap-4"> + <button + className="btn-small-light" + onClick={() => setOpenThemeSelector(true)} + > + <LuPalette className="text-[16px]" /> + <span className="hidden md:block">Change Theme</span> + </button> + + <button className="btn-small-light" onClick={handleDeleteResume}> + <LuTrash2 className="text-[16px]" /> + <span className="hidden md:block">Delete</span> + </button> + + <button + className="btn-small-light" + onClick={() => setOpenPreviewModal(true)} + > + <LuDownload className="text-[16px]" /> + <span className="hidden md:block">Preview & Download</span> + </button> + </div> + </div> + + <div className="grid grid-cols-1 md:grid-cols-2 gap-5"> + <div className="bg-white rounded-lg border border-purple-100 overflow-hidden"> + <StepProgress progress={progress} /> + + {renderForm()} + + <div className="mx-5"> + {errorMsg && ( + <div className="flex items-center gap-2 text-[11px] font-medium text-amber-600 bg-amber-100 px-2 py-0.5 my-1 rounded"> + <LuCircleAlert className="text-md" /> {errorMsg} + </div> + )} + + <div className="flex items-end justify-end gap-3 mt-3 mb-5"> + <button + className="btn-small-light" + onClick={goBack} + disabled={isLoading} + > + <LuArrowLeft className="text-[16px]" /> + Back + </button> + <button + className="btn-small-light" + onClick={uploadResumeImages} + disabled={isLoading} + > + <LuSave className="text-[16px]" /> + {isLoading ? "Updating..." : "Save & Exit"} + </button> + <button + className="btn-small" + onClick={validateAndNext} + disabled={isLoading} + > + {currentPage === "additionalInfo" && ( + <LuDownload className="text-[16px]" /> + )} + + {currentPage === "additionalInfo" + ? "Preview & Download" + : "Next"} + {currentPage !== "additionalInfo" && ( + <LuArrowLeft className="text-[16px] rotate-180" /> + )} + </button> + </div> + </div> + </div> + + <div ref={resumeRef} className="h-[100vh]"> + {/* Resume Template */} + <RenderResume + templateId={resumeData?.template?.theme || ""} + resumeData={resumeData} + colorPalette={resumeData?.template?.colorPalette || []} + containerWidth={baseWidth} + /> + </div> + </div> + </div> + + <Modal + isOpen={openThemeSelector} + onClose={() => setOpenThemeSelector(false)} + title="Change Theme" + > + <div className="w-[90vw] h-[80vh]"> + <ThemeSelector + selectedTheme={resumeData?.template} + setSelectedTheme={(value) => { + setResumeData((prevState) => ({ + ...prevState, + template: value || prevState.template, + })); + }} + resumeData={null} + onClose={() => setOpenThemeSelector(false)} + /> + </div> + </Modal> + + <Modal + isOpen={openPreviewModal} + onClose={() => setOpenPreviewModal(false)} + title={resumeData.title} + showActionBtn + actionBtnText="Download" + actionBtnIcon={<LuDownload className="text-[16px]" />} + onActionClick={() => reactToPrintFn()} + > + <div ref={resumeDownloadRef} className="w-[98vw] h-[90vh]"> + <RenderResume + templateId={resumeData?.template?.theme || ""} + resumeData={resumeData} + colorPalette={resumeData?.template?.colorPalette || []} + /> + </div> + </Modal> + </DashboardLayout> + ); +}; + +export default EditResume; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/AdditionalInfoFrom.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/AdditionalInfoFrom.jsx new file mode 100644 index 0000000..2c2306c --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/AdditionalInfoFrom.jsx @@ -0,0 +1,116 @@ +import React from "react"; +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; +import RatingInput from "../../../components/ResumeSections/RatingInput"; + +const AdditionalInfoFrom = ({ + languages, + interests, + updateArrayItem, + addArrayItem, + removeArrayItem, +}) => { + return <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900">Additional Info</h2> + + {/* Languages Section */} + <div className="mt-6"> + <h3 className="text-sm font-semibold text-gray-700 mb-2">Languages</h3> + <div className="flex flex-col gap-4"> + {languages?.map((lang, index) => ( + <div + key={index} + className="border border-gray-200 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4 items-start"> + <Input + label="Language" + placeholder="e.g. English" + value={lang.name || ""} + onChange={({ target }) => + updateArrayItem("languages", index, "name", target.value) + } + /> + + <div> + <label className="text-xs font-medium text-slate-600 mb-7 block"> + Proficiency + </label> + <RatingInput + value={lang.progress || 0} + onChange={(value) => + updateArrayItem("languages", index, "progress", value) + } + total={5} + activeColor="#0ea5e9" + inactiveColor="#e0f2fe" + /> + </div> + </div> + + {languages.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem("languages", index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => addArrayItem("languages", { name: "", progress: 0 })} + > + <LuPlus /> Add Language + </button> + </div> + </div> + + {console.log(interests)} + + {/* Interests Section */} + <div className="mt-8 mb-4"> + <h3 className="text-sm font-semibold text-gray-700">Interests</h3> + <div className="flex flex-col"> + {interests?.map((interest, index) => ( + <div + key={index} + className="relative rounded-lg" + > + <Input + placeholder="e.g. Reading" + value={interest || ""} + onChange={({ target }) => + updateArrayItem("interests", index, null, target.value) + } + /> + + {interests.length > 1 && ( + <button + type="button" + className="absolute top-6.5 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem("interests", index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => addArrayItem("interests", "")} + > + <LuPlus /> Add Interest + </button> + </div> + </div> + </div> +}; + +export default AdditionalInfoFrom; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/CertificationInfoFrom.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/CertificationInfoFrom.jsx new file mode 100644 index 0000000..803e657 --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/CertificationInfoFrom.jsx @@ -0,0 +1,81 @@ +import React from "react"; +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; + +const CertificationInfoFrom = ({ + certifications, + updateArrayItem, + addArrayItem, + removeArrayItem, +}) => { + return <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900">Certifications</h2> + + <div className="mt-4 flex flex-col gap-4 mb-3"> + {certifications.map((cert, index) => ( + <div + key={index} + className="border border-gray-200/80 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Input + label="Certificate Title" + placeholder="Fullstack Web Developer" + type="text" + value={cert.title || ""} + onChange={({ target }) => + updateArrayItem(index, "title", target.value) + } + /> + + <Input + label="Issuer" + placeholder="Coursera / Google / etc." + type="text" + value={cert.issuer || ""} + onChange={({ target }) => + updateArrayItem(index, "issuer", target.value) + } + /> + + <Input + label="Year" + placeholder="2024" + type="text" + value={cert.year || ""} + onChange={({ target }) => + updateArrayItem(index, "year", target.value) + } + /> + </div> + + {certifications.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem(index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => + addArrayItem({ + title: "", + issuer: "", + year: "", + }) + } + > + <LuPlus /> Add Certification + </button> + </div> + </div> +}; + +export default CertificationInfoFrom; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ContactInfoForm.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ContactInfoForm.jsx new file mode 100644 index 0000000..a834112 --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ContactInfoForm.jsx @@ -0,0 +1,68 @@ +import React from 'react' +import Input from "../../../components/Inputs/Input"; + +const ContactInfoForm = ({contactInfo, updateSection}) => { + return ( + <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900"> + Contact Information + </h2> + + <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4"> + <div className="col-span-2"> + <Input + label="Address" + placeholder="Short Address" + type="text" + value={contactInfo.location || ""} + onChange={({ target }) => updateSection("location", target.value)} + /> + </div> + + <Input + label="Email" + placeholder="john@example.com" + type="email" + value={contactInfo.email || ""} + onChange={({ target }) => updateSection("email", target.value)} + /> + + <Input + label="Phone Number" + placeholder="9876543210" + type="text" + value={contactInfo.phone || ""} + onChange={({ target }) => updateSection("phone", target.value)} + /> + + <Input + label="LinkedIn" + placeholder="https://linkedin.com/in/username" + type="text" + value={contactInfo.linkedin || ""} + onChange={({ target }) => updateSection("linkedin", target.value)} + /> + + <Input + label="GitHub" + placeholder="https://github.com/username" + type="text" + value={contactInfo.github || ""} + onChange={({ target }) => updateSection("github", target.value)} + /> + + <div className="md:col-span-2"> + <Input + label="Portfolio / Website" + placeholder="https://yourwebsite.com" + type="text" + value={contactInfo.website || ""} + onChange={({ target }) => updateSection("website", target.value)} + /> + </div> + </div> + </div> + ) +} + +export default ContactInfoForm \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/EducationDetailsForm.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/EducationDetailsForm.jsx new file mode 100644 index 0000000..87bc812 --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/EducationDetailsForm.jsx @@ -0,0 +1,90 @@ +import React from "react"; +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; + +const EducationDetailsForm = ({ + educationInfo, + updateArrayItem, + addArrayItem, + removeArrayItem, +}) => { + return <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900">Education</h2> + + <div className="mt-4 flex flex-col gap-4 mb-3"> + {educationInfo.map((education, index) => ( + <div + key={index} + className="border border-gray-200/80 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Input + label="Degree" + placeholder="B.Tech in Computer Science" + type="text" + value={education.degree || ""} + onChange={({ target }) => + updateArrayItem(index, "degree", target.value) + } + /> + + <Input + label="Institution" + placeholder="XYZ University" + type="text" + value={education.institution || ""} + onChange={({ target }) => + updateArrayItem(index, "institution", target.value) + } + /> + + <Input + label="Start Date" + type="month" + value={education.startDate || ""} + onChange={({ target }) => + updateArrayItem(index, "startDate", target.value) + } + /> + + <Input + label="End Date" + type="month" + value={education.endDate || ""} + onChange={({ target }) => + updateArrayItem(index, "endDate", target.value) + } + /> + </div> + + {educationInfo.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem(index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => + addArrayItem({ + degree: "", + institution: "", + startDate: "", + endDate: "", + }) + } + > + <LuPlus /> Add Education + </button> + </div> + </div> +}; + +export default EducationDetailsForm; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProfileInfoForm.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProfileInfoForm.jsx new file mode 100644 index 0000000..cd7cf37 --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProfileInfoForm.jsx @@ -0,0 +1,58 @@ +import React from 'react' +import ProfilePhotoSelector from "../../../components/Inputs/ProfilePhotoSelector"; +import Input from "../../../components/Inputs/Input"; + +const ProfileInfoForm = ({profileData, updateSection}) => { + return ( + <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900"> + Personal Information + </h2> + + <div className="mt-4"> + <ProfilePhotoSelector + image={profileData?.profileImg || profileData?.profilePreviewUrl} + setImage={(value) => updateSection("profileImg", value)} + preview={profileData?.profilePreviewUrl} + setPreview={(value) => updateSection("profilePreviewUrl", value)} + /> + + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Input + value={profileData.fullName || ""} + onChange={({ target }) => updateSection("fullName", target.value)} + label="Full Name" + placeholder="John" + type="text" + /> + + <Input + value={profileData.designation || ""} + onChange={({ target }) => + updateSection("designation", target.value) + } + label="Designation" + placeholder="UI Designer" + type="text" + /> + + <div className="col-span-2 mt-3"> + <label className="text-xs font-medium text-slate-600"> + Summary + </label> + + <textarea + placeholder="Short Introduction" + className="form-input" + rows={4} + value={profileData.summary || ""} + onChange={({ target }) => updateSection("summary", target.value)} + /> + </div> + </div> + </div> + </div> + ) +} + +export default ProfileInfoForm \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProjectsDetailFrom.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProjectsDetailFrom.jsx new file mode 100644 index 0000000..16b727c --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/ProjectsDetailFrom.jsx @@ -0,0 +1,101 @@ +import React from "react"; +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; + +const ProjectsDetailFrom = ({ + projectInfo, + updateArrayItem, + addArrayItem, + removeArrayItem, +}) => { + return <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900">Projects</h2> + + <div className="mt-4 flex flex-col gap-4 mb-3"> + {projectInfo.map((project, index) => ( + <div + key={index} + className="border border-gray-200/80 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <div className="col-span-2"> + <Input + label="Project Title" + placeholder="Portfolio Website" + type="text" + value={project.title || ""} + onChange={ + ({ target }) => + updateArrayItem(index, "title", target.value) + } + /> + </div> + + <div className="col-span-2"> + <label className="text-xs font-medium text-slate-600"> + Description + </label> + <textarea + placeholder="Short description about the project" + className="form-input w-full mt-1" + rows={3} + value={project.description || ""} + onChange={ + ({ target }) => + updateArrayItem(index, "description", target.value) + } + /> + </div> + + <Input + label="GitHub Link" + placeholder="https://github.com/username/project" + type="url" + value={project.github || ""} + onChange={({ target }) => + updateArrayItem(index, "github", target.value) + } + /> + + <Input + label="Live Demo URL" + placeholder="https://yourproject.live" + type="url" + value={project.liveDemo || ""} + onChange={({ target }) => + updateArrayItem(index, "liveDemo", target.value) + } + /> + </div> + + {projectInfo.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem(index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => + addArrayItem({ + title: "", + description: "", + github: "", + liveDemo: "", + }) + } + > + <LuPlus /> Add Project + </button> + </div> + </div> +}; + +export default ProjectsDetailFrom; diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/SkillsInfoForm.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/SkillsInfoForm.jsx new file mode 100644 index 0000000..5a6a54a --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/SkillsInfoForm.jsx @@ -0,0 +1,73 @@ +import React from 'react' +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; +import RatingInput from '../../../components/ResumeSections/RatingInput'; + +const SkillsInfoForm = ({skillsInfo, updateArrayItem, addArrayItem, removeArrayItem}) => { + return ( + <div className="px-5 pt-3"> + <h2 className="text-lg font-semibold text-gray-900">Skills</h2> + + <div className="mt-4 flex flex-col gap-4 mb-3"> + {skillsInfo.map((skill, index) => ( + <div + key={index} + className="border border-gray-200/80 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Input + label="Skill Name" + placeholder="JavaScript" + type="text" + value={skill.name || ""} + onChange={ + ({ target }) => + updateArrayItem(index, "name", target.value) + } + /> + + <div className="flex flex-col"> + <label className="text-[13px] text-slate-800 mb-1"> + Proficiency ({skill.progress / 20 || 0}/5) + </label> + <div className="mt-5"> + <RatingInput + value={skill.progress || 0} + total={5} + onChange={(newValue) => + updateArrayItem(index, "progress", newValue) + } + /> + </div> + </div> + </div> + + {skillsInfo.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem(index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => + addArrayItem({ + name: "", + progress: 0, + }) + } + > + <LuPlus /> Add Skill + </button> + </div> + </div> + ) +} + +export default SkillsInfoForm \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/Forms/WorkExperienceForm.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/WorkExperienceForm.jsx new file mode 100644 index 0000000..3c2840f --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/Forms/WorkExperienceForm.jsx @@ -0,0 +1,103 @@ +import React from 'react' +import Input from "../../../components/Inputs/Input"; +import { LuPlus, LuTrash2 } from "react-icons/lu"; + +const WorkExperienceForm = ({workExperience, updateArrayItem, addArrayItem, removeArrayItem}) => { + return ( + <div className="px-5 pt-5"> + <h2 className="text-lg font-semibold text-gray-900">Work Experience</h2> + + <div className="mt-4 flex flex-col gap-4 mb-3"> + {workExperience.map((experience, index) => ( + <div + key={index} + className="border border-gray-200/80 p-4 rounded-lg relative" + > + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Input + label="Company" + placeholder="ABC Corp" + type="text" + value={experience.company || ""} + onChange={({ target }) => + updateArrayItem(index, "company", target.value) + } + /> + + <Input + label="Role" + placeholder="Frontend Developer" + type="text" + value={experience.role || ""} + onChange={({ target }) => + updateArrayItem(index, "role", target.value) + } + /> + + <Input + label="Start Date" + type="month" + value={experience.startDate || ""} + onChange={({ target }) => + updateArrayItem(index, "startDate", target.value) + } + /> + + <Input + label="End Date" + type="month" + value={experience.endDate || ""} + onChange={({ target }) => + updateArrayItem(index, "endDate", target.value) + } + /> + </div> + + <div className="mt-4"> + <label className="text-xs font-medium text-slate-600"> + Description + </label> + <textarea + placeholder="What did you do in this role?" + className="form-input w-full mt-1" + rows={3} + value={experience.description || ""} + onChange={({ target }) => + updateArrayItem(index, "description", target.value) + } + /> + </div> + + {workExperience.length > 1 && ( + <button + type="button" + className="absolute top-3 right-3 text-sm text-red-600 hover:underline cursor-pointer" + onClick={() => removeArrayItem(index)} + > + <LuTrash2 /> + </button> + )} + </div> + ))} + + <button + type="button" + className="self-start flex items-center gap-2 px-4 py-2 rounded bg-purple-100 text-purple-800 text-sm font-medium hover:bg-purple-200 cursor-pointer" + onClick={() => + addArrayItem({ + company: "", + role: "", + startDate: "", + endDate: "", + description: "", + }) + } + > + <LuPlus /> Add Work Experience + </button> + </div> + </div> + ) +} + +export default WorkExperienceForm \ No newline at end of file diff --git a/frontend/resume-builder/src/pages/ResumeUpdate/ThemeSelector.jsx b/frontend/resume-builder/src/pages/ResumeUpdate/ThemeSelector.jsx new file mode 100644 index 0000000..a3a7f5c --- /dev/null +++ b/frontend/resume-builder/src/pages/ResumeUpdate/ThemeSelector.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + DUMMY_RESUME_DATA, + resumeTemplates, + themeColorPalette, +} from "../../utils/data"; +import { LuCircleCheckBig } from "react-icons/lu"; +import Tabs from "../../components/Tabs"; +import TemplateCard from "../../components/Cards/TemplateCard"; +import RenderResume from "../../components/ResumeTemplates/RenderResume"; + +const TAB_DATA = [{ label: "Templates" }, { label: "Color Palettes" }]; + +const ThemeSelector = ({ + selectedTheme, + setSelectedTheme, + resumeData, + onClose, +}) => { + const resumeRef = useRef(null); + const [baseWidth, setBaseWidth] = useState(800); + + const [tabValue, setTabValue] = useState("Templates"); + const [selectedColorPalette, setSelectedColorPalette] = useState({ + colors: selectedTheme?.colorPalette, + index: -1, + }); + const [selectedTemplate, setSelectedTemplate] = useState({ + theme: selectedTheme?.theme || "", + index: -1, + }); + + // Handle Theme Change + const handleThemeSelection = () => { + setSelectedTheme({ + colorPalette: selectedColorPalette?.colors, + theme: selectedTemplate?.theme, + }); + onClose(); + }; + + const updateBaseWidth = () => { + if (resumeRef.current) { + setBaseWidth(resumeRef.current.offsetWidth); + } + }; + + useEffect(() => { + updateBaseWidth(); + window.addEventListener("resize", updateBaseWidth); + + return () => { + window.removeEventListener("resize", updateBaseWidth); + }; + }, []); + + return <div className="container mx-auto px-2 md:px-0"> + <div className="flex items-center justify-between mb-5 mt-2"> + <Tabs tabs={TAB_DATA} activeTab={tabValue} setActiveTab={setTabValue} /> + + <button + className="btn-small-light" + onClick={() => handleThemeSelection()} + > + <LuCircleCheckBig className="text-[16px]" /> + Done + </button> + </div> + + <div className="grid grid-cols-12 gap-5"> + <div className="col-span-12 md:col-span-5 bg-white"> + <div className="grid grid-cols-2 gap-5 max-h-[80vh] overflow-scroll custom-scrollbar md:pr-5"> + {tabValue === "Templates" && + resumeTemplates.map((template, index) => ( + <TemplateCard + key={`templates_${index}`} + thumbnailImg={template.thumbnailImg} + isSelected={selectedTemplate?.index === index} + onSelect={() => + setSelectedTemplate({ theme: template.id, index }) + } + /> + ))} + + {tabValue === "Color Palettes" && + themeColorPalette.themeOne.map((colors, index) => ( + <ColorPalette + key={`palette_${index}`} + colors={colors} + isSelected={selectedColorPalette?.index === index} + onSelect={() => setSelectedColorPalette({ colors, index })} + /> + ))} + </div> + </div> + <div className="col-span-12 md:col-span-7 bg-white -mt-3" ref={resumeRef}> + <RenderResume + templateId={selectedTemplate?.theme || ""} + resumeData={resumeData || DUMMY_RESUME_DATA} + containerWidth={baseWidth} + colorPalette={selectedColorPalette?.colors || []} + /> + </div> + </div> + </div> +}; + +export default ThemeSelector; + +const ColorPalette = ({ colors, isSelected, onSelect }) => { + return ( + <div + className={`h-28 bg-purple-50 flex rounded-lg overflow-hidden border-2 ${ + isSelected ? "border-purple-500" : "border-none" + }`} + > + {colors.map((color, index) => ( + <div + key={`color_${index}`} + className="flex-1" + style={{ backgroundColor: colors[index] }} + onClick={onSelect} + /> + ))} + </div> + ); +}; \ No newline at end of file diff --git a/frontend/resume-builder/src/utils/apiPaths.js b/frontend/resume-builder/src/utils/apiPaths.js new file mode 100644 index 0000000..7a8dbbd --- /dev/null +++ b/frontend/resume-builder/src/utils/apiPaths.js @@ -0,0 +1,23 @@ +export const BASE_URL = "http://localhost:8000"; + +// utils/apiPaths.js +export const API_PATHS = { + AUTH: { + REGISTER: "/api/auth/register", // Signup + LOGIN: "/api/auth/login", // Authenticate user & return JWT token + GET_PROFILE: "/api/auth/profile", // Get logged-in user details + }, + + RESUME: { + CREATE: "/api/resume", // POST - Create a new resume + GET_ALL: "/api/resume", // GET - Get all resumes of logged-in user + GET_BY_ID: (id) => `/api/resume/${id}`, // GET - Get a specific resume + UPDATE: (id) => `/api/resume/${id}`, // PUT - Update a resume + DELETE: (id) => `/api/resume/${id}`, // DELETE - Delete a resume + UPLOAD_IMAGES: (id) => `/api/resume/${id}/upload-images`, // PUT - Upload Thumbnail and Resume profile img + }, + + IMAGE: { + UPLOAD_IMAGE: "api/auth/upload-image", + }, +}; diff --git a/frontend/resume-builder/src/utils/axiosInstance.js b/frontend/resume-builder/src/utils/axiosInstance.js new file mode 100644 index 0000000..9c4616f --- /dev/null +++ b/frontend/resume-builder/src/utils/axiosInstance.js @@ -0,0 +1,48 @@ +import axios from "axios"; +import { BASE_URL } from "./apiPaths"; + +const axiosInstance = axios.create({ + baseURL: BASE_URL, + timeout: 10000, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, +}); + +// Request Interceptor +axiosInstance.interceptors.request.use( + (config) => { + const accessToken = localStorage.getItem("token"); + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// Response Interceptor +axiosInstance.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + // Handle common errors globally + if (error.response) { + if (error.response.status === 401) { + // Redirect to login page + window.location.href = "/"; + } else if (error.response.status === 500) { + console.error("Server error. Please try again later."); + } + } else if (error.code === "ECONNABORTED") { + console.error("Request timeout. Please try again."); + } + return Promise.reject(error); + } +); + +export default axiosInstance; diff --git a/frontend/resume-builder/src/utils/data.js b/frontend/resume-builder/src/utils/data.js new file mode 100644 index 0000000..df6237e --- /dev/null +++ b/frontend/resume-builder/src/utils/data.js @@ -0,0 +1,153 @@ +import TEMPLATE_ONE_IMG from '../assets/template-one.png' +import TEMPLATE_TWO_IMG from '../assets/template-two.png' +import TEMPLATE_THREE_IMG from '../assets/template-three.png' + +export const resumeTemplates = [ + { + id:'01', + thumbnailImg: TEMPLATE_ONE_IMG, + colorPaletteCode: 'themeOne' + }, + { + id:'02', + thumbnailImg: TEMPLATE_TWO_IMG, + colorPaletteCode: 'themeTwo' + }, + { + id:'03', + thumbnailImg: TEMPLATE_THREE_IMG, + colorPaletteCode: 'themeThree' + }, +] + +export const themeColorPalette = { + themeOne: [ + ["#EBFDFF", "#A1F4FD", "#CEFAFE", "#00B8DB", "#4A5565"], + + ["#E9FBF8", "#B4EFE7", "#93E2DA", "#2AC9A0", "#3D4C5A"], + ["#F5F4FF", "#E0DBFF", "#C9C2F8", "#8579D1", "#4B4B5C"], + ["#F0FAFF", "#D6F0FF", "#AFDEFF", "#3399FF", "#445361"], + ["#FFF5F7", "#FFE0EC", "#FAC6D4", "#F6729C", "#5A5A5A"], + ["#F9FAFB", "#E4E7EB", "#CBD5E0", "#7F9CF5", "#2D3748"], + + ["#F4FFFD", "#D3FDF2", "#B0E9D4", "#34C79D", "#384C48"], + ["#FFF7F0", "#FFE6D9", "#FFD2BA", "#FF9561", "#4C4743"], + ["#F9FCFF", "#E3F0F9", "#C0DDEE", "#6CA6CF", "#46545E"], + ["#FFFDF6", "#FFF4D7", "#FFE7A0", "#FFD000", "#57534E"], + ["#EFFCFF", "#C8F0FF", "#99E0FF", "#007BA7", "#2B3A42"], + + ["#F7F7F7", "#E4E4E4", "#CFCFCF", "#4A4A4A", "#222222"], + ["#E3F2FD", "#90CAF9", "#a8d2f4", "#1E88E5", "#0D47A1"], + ], +}; + +export const DUMMY_RESUME_DATA = { + profileInfo: { + profileImg: null, + previewUrl: "", + fullName: "John Doe", + designation: "Senior Software Engineer", + summary: + "Passionate and results-driven developer with 6+ years of experience building full-stack web applications using modern technologies like React, Node.js, and MongoDB.", + }, + contactInfo: { + email: "john.doe@example.com", + phone: "+1234567890", + location:'#12 Anywhere, Any City, Any Country', + linkedin: "https://linkedin.com/timetoprogram", + github: "https://github.com/timetoprogram", + website: "https://timetoprogram.com", + }, + workExperience: [ + { + company: "Tech Solutions", + role: "Senior Frontend Engineer", + startDate: "2022-03", + endDate: "2025-04", + description: + "Leading the frontend team to build scalable enterprise applications using React, Tailwind CSS, and TypeScript.", + }, + { + company: "Coding Dev", + role: "Full Stack Developer", + startDate: "2020-01", + endDate: "2022-02", + description: + "Worked on cross-functional teams developing full-stack solutions with React, Node.js, and MongoDB. Improved performance by 30% through code optimization.", + }, + { + company: "Startup Company", + role: "Junior Web Developer", + startDate: "2018-06", + endDate: "2019-12", + description: + "Built responsive websites for startups and small businesses. Maintained legacy code and collaborated with designers to enhance UX/UI.", + }, + ], + education: [ + { + degree: "M.Sc. Software Engineering", + institution: "Tech University", + startDate: "2021-08", + endDate: "2023-06", + }, + { + degree: "B.Sc. Computer Science", + institution: "State University", + startDate: "2017-08", + endDate: "2021-05", + }, + { + degree: "High School Diploma", + institution: "Central High School", + startDate: "2015-06", + endDate: "2017-05", + }, + ], + skills: [ + { name: "JavaScript", progress: 95 }, + { name: "React", progress: 90 }, + { name: "Node.js", progress: 85 }, + { name: "TypeScript", progress: 80 }, + { name: "MongoDB", progress: 75 }, + ], + projects: [ + { + title: "Project Manager App", + description: + "A task and team management app built with MERN stack. Includes user roles, real-time notifications, and drag-and-drop task boards.", + github: "https://github.com/timetoprogram/project-manager-app", + }, + { + title: "E-Commerce Platform", + description: + "An e-commerce site built with Next.js and Stripe integration. Supports cart, payments, order history, and admin dashboard.", + liveDemo: "https://ecommerce-demo.timetoprogram.com", + }, + { + title: "Blog CMS", + description: + "A custom CMS for blogging using Express and React. Includes WYSIWYG editor, markdown support, and user management.", + github: "https://github.com/timetoprogram/blog-cms", + liveDemo: "https://blogcms.timetoprogram.dev", + }, + ], + certifications: [ + { + title: "Full Stack Web Developer", + issuer: "Udemy", + year: "2023", + }, + { + title: "React Advanced Certification", + issuer: "Coursera", + year: "2022", + }, + ], + languages: [ + { name: "English", progress: 100 }, + { name: "Spanish", progress: 70 }, + { name: "French", progress: 40 }, + ], + interests: ["Reading", "Open Source Contribution", "Hiking"], +}; \ No newline at end of file diff --git a/frontend/resume-builder/src/utils/helper.js b/frontend/resume-builder/src/utils/helper.js new file mode 100644 index 0000000..edf5e7d --- /dev/null +++ b/frontend/resume-builder/src/utils/helper.js @@ -0,0 +1,111 @@ +import moment from 'moment' +import html2canvas from "html2canvas"; + +export const validateEmail = (email) => { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +}; + +// get lightest average color +export const getLightColorFromImage = (imageUrl) => { + return new Promise((resolve, reject) => { + // Check if imageUrl is valid + if (!imageUrl || typeof imageUrl !== 'string') { + return reject(new Error('Invalid image URL')); + } + + const img = new Image(); + + // If not a base64 string, set crossOrigin (important for CORS) + if (!imageUrl.startsWith('data:')) { + img.crossOrigin = 'anonymous'; + } + + img.src = imageUrl; + + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + + let r = 0, g = 0, b = 0, count = 0; + + for (let i = 0; i < imageData.length; i += 4) { + const red = imageData[i]; + const green = imageData[i + 1]; + const blue = imageData[i + 2]; + const brightness = (red + green + blue) / 3; + + // Only count light pixels (tweak threshold as needed) + if (brightness > 180) { + r += red; + g += green; + b += blue; + count++; + } + } + + if (count === 0) { + resolve('#ffffff'); // fallback if no bright pixels found + } else { + r = Math.round(r / count); + g = Math.round(g / count); + b = Math.round(b / count); + resolve(`rgb(${r}, ${g}, ${b})`); + } + }; + + img.onerror = (e) => { + console.error('❌ Failed to load image:', e); + reject(new Error('Image could not be loaded or is blocked by CORS.')); + }; + }); +}; + +// Eg: Mar 2025 +export function formatYearMonth(yearMonth) { + return yearMonth ? moment(yearMonth, "YYYY-MM").format("MMM YYYY") : ""; +} + +export const fixTailwindColors = (element) => { + const elements = element.querySelectorAll("*"); + + elements.forEach((el) => { + const style = window.getComputedStyle(el); + + ["color", "backgroundColor", "borderColor"].forEach((prop) => { + const value = style[prop]; + if (value.includes("oklch")) { + el.style[prop] = "#000"; // or any safe fallback + } + }); + }); +}; + +// convert component to image +export async function captureElementAsImage(element) { + if (!element) throw new Error("No element provided"); + + const canvas = await html2canvas(element); + return canvas.toDataURL("image/png"); +} + +// Utility to convert base64 data URL to a File object +export const dataURLtoFile = (dataUrl, fileName) => { + const arr = dataUrl.split(","); + const mime = arr[0].match(/:(.*?);/)[1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + return new File([u8arr], fileName, { type: mime }); +}; \ No newline at end of file diff --git a/frontend/resume-builder/src/utils/uploadImage.js b/frontend/resume-builder/src/utils/uploadImage.js new file mode 100644 index 0000000..cdb8d20 --- /dev/null +++ b/frontend/resume-builder/src/utils/uploadImage.js @@ -0,0 +1,22 @@ +import { API_PATHS } from './apiPaths'; +import axiosInstance from './axiosInstance'; + +const uploadImage = async (imageFile) => { + const formData = new FormData(); + // Append image file to form data + formData.append('image', imageFile); + + try { + const response = await axiosInstance.post(API_PATHS.IMAGE.UPLOAD_IMAGE, formData, { + headers: { + 'Content-Type': 'multipart/form-data', // Set header for file upload + }, + }); + return response.data; // Return response data + } catch (error) { + console.error('Error uploading the image:', error); + throw error; // Rethrow error for handling + } +}; + +export default uploadImage; diff --git a/frontend/resume-builder/vite.config.js b/frontend/resume-builder/vite.config.js new file mode 100644 index 0000000..c4069b7 --- /dev/null +++ b/frontend/resume-builder/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], +})