diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5a007a8 --- /dev/null +++ b/.env.example @@ -0,0 +1,45 @@ +# Email Configuration for Production Deployment +# ============================================== + +# Gmail SMTP Configuration (recommended for production) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +EMAIL_USER=your-gmail-address@gmail.com +# Use App Password, NOT your regular Gmail password +# Generate at: https://myaccount.google.com/apppasswords +EMAIL_PASS=your-app-password-here + +# Advanced SMTP Settings (optional, defaults are optimized) +SMTP_MAX_CONNECTIONS=3 +SMTP_MAX_MESSAGES=50 + +# Environment +NODE_ENV=production + +# Alternative Email Providers (uncomment to use) +# ================================================ + +# Outlook/Hotmail +# SMTP_HOST=smtp-mail.outlook.com +# SMTP_PORT=587 +# SMTP_SECURE=false + +# Yahoo Mail +# SMTP_HOST=smtp.mail.yahoo.com +# SMTP_PORT=587 +# SMTP_SECURE=false + +# SendGrid (Recommended for high-volume production) +# SMTP_HOST=smtp.sendgrid.net +# SMTP_PORT=587 +# SMTP_SECURE=false +# EMAIL_USER=apikey +# EMAIL_PASS=your-sendgrid-api-key + +# Mailgun +# SMTP_HOST=smtp.mailgun.org +# SMTP_PORT=587 +# SMTP_SECURE=false +# EMAIL_USER=your-mailgun-smtp-username +# EMAIL_PASS=your-mailgun-smtp-password \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/EMAIL_SETUP_GUIDE.md b/EMAIL_SETUP_GUIDE.md new file mode 100644 index 0000000..f928a1e --- /dev/null +++ b/EMAIL_SETUP_GUIDE.md @@ -0,0 +1,136 @@ +# Email Configuration Guide for Production Deployment + +## The Problem +Your application works in localhost but fails to send emails in production (Render) due to SMTP connection timeouts. This is a common issue when deploying to cloud platforms. + +## Root Cause +1. **Port Configuration**: Many cloud platforms block port 465 (SMTP over SSL) +2. **Connection Timeouts**: Production environments have stricter timeout settings +3. **Gmail Security**: Gmail requires App Passwords for production applications +4. **Network Restrictions**: Some cloud providers have firewall restrictions + +## Solutions Implemented + +### 1. Updated SMTP Configuration +- Changed default port from 465 to 587 (STARTTLS) +- Added production-optimized timeout settings +- Implemented connection pooling with rate limiting +- Added proper TLS configuration + +### 2. Added Retry Mechanism +- Automatic retry with exponential backoff +- Better error logging for debugging +- Non-blocking email failures (won't crash the application) + +### 3. Environment-Specific Settings +- Different configurations for development vs production +- Comprehensive error logging +- Connection health checks + +## Setup Instructions for Render + +### Step 1: Configure Gmail App Password +1. Go to [Google Account Settings](https://myaccount.google.com) +2. Enable 2-Factor Authentication if not already enabled +3. Go to [App Passwords](https://myaccount.google.com/apppasswords) +4. Generate an App Password for your application +5. Use this App Password in your environment variables (NOT your regular password) + +### Step 2: Set Environment Variables in Render +In your Render dashboard, add these environment variables: + +```bash +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=false +EMAIL_USER=your-gmail-address@gmail.com +EMAIL_PASS=your-app-password-here +NODE_ENV=production +``` + +### Step 3: Alternative Email Providers (if Gmail still fails) + +#### SendGrid (Recommended for Production) +```bash +SMTP_HOST=smtp.sendgrid.net +SMTP_PORT=587 +SMTP_SECURE=false +EMAIL_USER=apikey +EMAIL_PASS=your-sendgrid-api-key +``` + +#### Mailgun +```bash +SMTP_HOST=smtp.mailgun.org +SMTP_PORT=587 +SMTP_SECURE=false +EMAIL_USER=your-mailgun-smtp-username +EMAIL_PASS=your-mailgun-smtp-password +``` + +### Step 4: Test the Configuration +Add this test endpoint to verify email functionality: + +```javascript +// Add to your routes +app.get('/test-email', async (req, res) => { + try { + await sendEmail( + 'test@example.com', + 'Test Email', + 'This is a test email from production' + ); + res.json({ success: true, message: 'Email sent successfully' }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message, + code: error.code + }); + } +}); +``` + +## Troubleshooting + +### Common Issues and Solutions + +1. **Connection Timeout (ETIMEDOUT)** + - Switch to port 587 instead of 465 + - Check if your hosting provider blocks SMTP ports + - Try alternative email providers like SendGrid + +2. **Authentication Failed** + - Ensure you're using App Password, not regular password + - Verify EMAIL_USER and EMAIL_PASS are correctly set + - Check if Less Secure Apps is enabled (not recommended) + +3. **TLS/SSL Issues** + - Use SMTP_SECURE=false with port 587 + - Set proper TLS configuration in code + +4. **Rate Limiting** + - Implemented automatic rate limiting (5 emails per second) + - Added retry mechanism for failed sends + +### Debug Steps +1. Check the email health endpoint: `GET /api/email-health` +2. Monitor application logs for detailed error messages +3. Test with a simple email first +4. Verify all environment variables are set correctly + +## Best Practices for Production + +1. **Use Dedicated Email Services**: Consider SendGrid, Mailgun, or AWS SES for production +2. **Monitor Email Delivery**: Set up logging and monitoring for email failures +3. **Implement Email Queues**: For high-volume applications, use a queue system +4. **Error Handling**: Never let email failures crash your application +5. **Security**: Always use App Passwords or API keys, never regular passwords + +## Code Changes Made + +1. **Enhanced SMTP Configuration**: Added timeout settings, rate limiting, and TLS configuration +2. **Retry Logic**: Automatic retry with exponential backoff for failed emails +3. **Async/Await**: Converted all email calls to async/await with proper error handling +4. **Better Logging**: Comprehensive error logging for debugging +5. **Non-blocking**: Email failures won't block the application flow \ No newline at end of file diff --git a/README.md b/README.md index 1747334..e8d9ec9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Fork Details +- Forked from Outpass Management System +- To Modify to convert it into an Institute Forms Portal +- Details of original project below + # Outpass Management System ## Project Overview diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..e34d45e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,11 @@ +node_modules +npm-debug.log +.env +.git +.gitignore +README.md +Dockerfile +.dockerignore +coverage +.nyc_output +.log \ No newline at end of file diff --git a/backend/.env b/backend/.env deleted file mode 100644 index 83624a0..0000000 --- a/backend/.env +++ /dev/null @@ -1,5 +0,0 @@ -PORT= 4001 -MongoDBURI="mongodb+srv://prajwalkoppad30:sWiDnWaAcpFA9GYC@cluster0.qen37.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" -JWT_SECRET=sleepyboiswinsleeplesscodingsaga -EMAIL_USER=prajwalkoppad30@gmail.com -EMAIL_PASS=mfst dgwr wtwp jslf \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..07a579b --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,23 @@ +# Use official Node.js LTS (Long Term Support) image +FROM node:22-alpine + +# Set working directory +WORKDIR /app + +# Copy package.json and package-lock.json (if available) +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy the rest of the application code +COPY . . + +# Set the port for the container +ENV PORT=5000 + +# Expose the port your app runs on (adjust if different) +EXPOSE 5000 + +# Command to run the application +CMD ["npm", "start"] \ No newline at end of file diff --git a/backend/controller/event.controller.js b/backend/controller/event.controller.js new file mode 100644 index 0000000..50d38d4 --- /dev/null +++ b/backend/controller/event.controller.js @@ -0,0 +1,1071 @@ +import EventApproval from "../models/event.model.js"; +import User from "../models/user.model.js"; +import nodemailer from "nodemailer"; +import dotenv from "dotenv"; + +dotenv.config(); + +// Utility function to determine semester and academic year based on date +const getSemesterInfo = (date) => { + const eventDate = new Date(date); + const month = eventDate.getMonth(); // 0-11 + const year = eventDate.getFullYear(); + + let semester, academicYear; + + // Assuming academic year starts in August and ends in July next year + // Fall semester: August - December + // Spring semester: January - July + + if (month >= 7) { // August (7) to December (11) + semester = "Autumn"; + academicYear = `${year}-${year + 1}`; + } else { // January (0) to July (6) + semester = "Spring"; + academicYear = `${year - 1}-${year}`; + } + + return { + semester: `${semester} ${academicYear.split('-')[semester === 'Autumn' ? 0 : 1]}`, + academicYear + }; +}; + +// Configure nodemailer +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS + } +}); + + +const sendEmail = async (to, subject, text, retries = 3) => { + const mailOptions = { + from: process.env.EMAIL_USER, + to, + subject, + text + }; + + console.log('Attempting to send email to:', to, 'with', retries, 'retries remaining'); + + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + console.error('Error sending email:', error); + } else { + console.log('Email sent:', info.response); + } + }); +}; + + +//emails of all members +const roleEmails = [ + { role: "general-secretary-technical", email: "gstech@iitdh.ac.in" }, + { role: "general-secretary-cultural", email: "nidhishadoshi05@gmail.com" }, + { role: "general-secretary-sports", email: "sports.secretary@example.com" }, + { role: "treasurer", email: "cs23bt009@iitdh.ac.in" }, + { role: "president", email: "cs23bt009@iitdh.ac.in" }, + { role: "faculty-in-charge", email: "nidhishadoshi05@gmail.com" }, + { role: "associate-dean", email: "cs23bt009@iitdh.ac.in" }, +]; + +const getEmailForRole = (role) => { + const match = roleEmails.find((entry) => entry.role === role); + return match ? match.email : null; +}; +const getEmailForCategory = (category) => { + const role = `general-secretary-${category.toLowerCase()}`; + const match = roleEmails.find((entry) => entry.role === role); + return match ? match.email : null; +}; + + +// Apply for Event approval + +export const applyForEventApproval = async (req, res) => { + try { + const { + userID, + eventName, + partOfGymkhanaCalendar, + clubName, + startDate, + endDate, + eventVenue, + sourceOfBudget, + estimatedBudget, + nameOfTheOrganizer, + designation, + email, + phoneNumber, + requirements, + anyAdditionalAmenities, + eventDescription, + internalParticipants, + externalParticipants, + listOfCollaboratingOrganizations, + } = req.body; + + console.log("Incoming Payload:", req.body); + + const userID_ = req.body.userID; // Ensure this is retrieved correctly (e.g., from the request or session). + + // Fetch user details to ensure they exist + const user = await User.findById(userID_); + if (!user) { + return res.status(404).json({ message: "User not found. Please log in again." }); + } + + // Set eventType based on user's type if club-secretary, else fallback to req.body.eventType + let eventType = req.body.eventType; + if (user.role === "club-secretary") { + eventType = user.type; + } + + const category = eventType; + const categoryEmail = getEmailForCategory(category); + + // Check for any existing event approval in progress for this user + const existingEvent = await EventApproval.findOne({ + userID_, + "approvals.status": { $in: ["Pending"] }, + }); + + if (existingEvent) { + return res.status(400).json({ message: "You already have a pending event approval request." }); + } + + // Determine semester and academic year based on start date + const semesterInfo = getSemesterInfo(startDate); + + // Create the initial approvals array + const approvals = [ + { role: "club-secretary", status: "Approved", comment: "" }, + { role: "general-secretary", status: "Pending", comment: "" }, + { role: "treasurer", status: "Pending", comment: "" }, + { role: "president", status: "Pending", comment: "" }, + { role: "faculty-in-charge", status: "Pending", comment: "" }, + { role: "associate-dean", status: "Pending", comment: "" }, + ]; + + // Create a new event approval request + const newEventApproval = new EventApproval({ + userID, + eventName, + partOfGymkhanaCalendar, + eventType, // Use the determined eventType + clubName, + startDate, + endDate, + semester: semesterInfo.semester, + academicYear: semesterInfo.academicYear, + eventVenue, + sourceOfBudget, + estimatedBudget, + nameOfTheOrganizer, + designation, + email, + phoneNumber, + requirements, + anyAdditionalAmenities, + eventDescription, + internalParticipants, + externalParticipants, + listOfCollaboratingOrganizations, + approvals, + }); + + // Save the new approval + const savedApproval = await newEventApproval.save(); + + // Update the user's `eventApproval` field with the new approval ID + user.eventApproval = savedApproval._id; + await user.save(); + if (categoryEmail) { + try { + await sendEmail( + categoryEmail, + `Event Approval Needed: ${eventName}`, + `A new ${category} event approval request has been submitted. Please review it at your earliest convenience.` + ); + console.log(`Email sent successfully to ${categoryEmail} for ${category} event approval.`); + } catch (emailError) { + console.error(`Failed to send email to ${categoryEmail}:`, emailError.message); + // Don't fail the entire request due to email issues + } + } else { + console.error(`No email found for category: ${category}`); + } + + + res.status(201).json({ + message: "Event approval request submitted successfully.", + eventApproval: newEventApproval, + }); + } catch (error) { + if (error.name === "ValidationError") { + console.error("Validation Error:", error.errors); + return res.status(400).json({ message: "Validation error", errors: error.errors }); + } + console.error("Error submitting event approval request:", error.message); + res.status(500).json({ message: "Internal server error" }); + } +}; + +export const getUserEvents = async (req, res) => { + const { userID } = req.body; + + try { + // Find events associated with the user's ID + const events = await EventApproval.find({ userID }); + if (!events || events.length === 0) { + return res.status(404).json({ message: "No events found for this user." }); + } + + res.status(200).json({ events }); + } catch (error) { + console.error("Error fetching user events:", error.message); + res.status(500).json({ message: "Internal server error." }); + } +}; + +//get pending approvals list +//put in staff dashboard Pending section + +const roleHierarchy = ["club-secretary", "general-secretary", "treasurer", "president", "faculty-in-charge", "associate-dean"]; + +export const getPendingApprovals = async (req, res) => { + const { role, category } = req.body; + + try { + if (!role) { + return res.status(400).json({ message: "Role is required." }); + } + + const roleIndex = roleHierarchy.indexOf(role); + if (roleIndex === -1) { + return res.status(400).json({ message: "Invalid role." }); + } + + // Find all applications with the specified role + let pendingApprovals = await EventApproval.find({ + "approvals.role": role, + }); + + // If no applications are found, return a response and exit + if (pendingApprovals.length === 0) { + return res.status(200).json({ message: "No applications found." }); + } + + // Filter out those with a status of 'Pending' or 'Query' + pendingApprovals = pendingApprovals.filter((approval) => { + const approvalStatus = approval.approvals.find( + (app) => app.role === role + ); + return approvalStatus && (approvalStatus.status === "Pending" || approvalStatus.status === "Query"); + }); + + // Ensure previous roles in the hierarchy are approved + pendingApprovals = pendingApprovals.filter((approval) => { + return roleHierarchy.slice(0, roleIndex).every((prevRole) => { + const prevApproval = approval.approvals.find((app) => app.role === prevRole); + return prevApproval && prevApproval.status === "Approved"; + }); + }); + + // If 'general-secretary', filter by event category + if (role === "general-secretary" && category) { + pendingApprovals = pendingApprovals.filter( + (approval) => approval.eventType === category + ); + } + + // Return filtered pending approvals + res.status(200).json(pendingApprovals); + } catch (error) { + console.error("Error fetching pending approvals:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Function to fetch event details by ID +export const getEventById = async (req, res) => { + const { id } = req.params; + + try { + // Find the event by ID + const event = await EventApproval.findById(id); + + if (!event) { + return res.status(404).json({ message: "Event not found." }); + } + + res.status(200).json(event); + } catch (error) { + console.error("Error fetching event details:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + + +export const getApprovedApplications = async (req, res) => { + const { role, category, status} = req.body; + + console.log("Received role:", role); + console.log("Received category:", category); + + try { + if (!role) { + return res.status(400).json({ message: "Role is required." }); + } + + // Fetch only events with matching role and approved status + let approvedApplications = await EventApproval.find({ + "approvals": { + $elemMatch: { + role: role, + status: "Approved" + } + } + }); + + console.log("Initial approved applications:", approvedApplications); + + if (approvedApplications.length === 0) { + return res.status(200).json([]); + } + + // Filter by category if role is 'general-secretary' + if (role === "general-secretary" && category) { + approvedApplications = approvedApplications.filter( + (approval) => approval.eventType === category + ); + } + + // Filter out applications with `endDate` before the current date + const currentDate = new Date(); + approvedApplications = approvedApplications.filter((approval) => { + const endDate = new Date(approval.endDate); + return currentDate <= endDate; + }); + + console.log("Final approved applications:", approvedApplications); + res.status(200).json(approvedApplications); + } catch (error) { + console.error("Error fetching approved applications:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Get Rejected Event Applications +export const getRejectedApplications = async (req, res) => { + const { role, category } = req.body; + + console.log("Received role:", role); + console.log("Received category:", category); + + try { + if (!role) { + return res.status(400).json({ message: "Role is required." }); + } + + // Fetch only events with matching role and rejected status + let rejectedApplications = await EventApproval.find({ + "approvals": { + $elemMatch: { + role: role, + status: "Rejected" + } + } + }); + + console.log("Initial rejected applications:", rejectedApplications); + + if (rejectedApplications.length === 0) { + return res.status(200).json([]); + } + + // Filter by category if role is 'general-secretary' + if (role === "general-secretary" && category) { + rejectedApplications = rejectedApplications.filter( + (approval) => approval.eventType === category + ); + } + + // Filter out applications with `endDate` before the current date (optional) + const currentDate = new Date(); + rejectedApplications = rejectedApplications.filter((approval) => { + const endDate = new Date(approval.endDate); + return currentDate <= endDate; // Only show future rejected events (optional logic) + }); + + console.log("Final rejected applications:", rejectedApplications); + res.status(200).json(rejectedApplications); + } catch (error) { + console.error("Error fetching rejected applications:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +//approve an event function + +// Function to approve an application based on the role +export const approveApplication = async (req, res) => { + const { applicationId, role } = req.body; + + try { + // Check if the role is valid + if (!role || !roleHierarchy.includes(role)) { + return res.status(400).json({ message: "Invalid or missing role." }); + } + + // Find the event approval by applicationId + const eventApproval = await EventApproval.findById(applicationId); + if (!eventApproval) { + return res.status(404).json({ message: "Event approval not found." }); + } + + // Find the index of the approval object corresponding to the given role + const approvalIndex = eventApproval.approvals.findIndex( + (approval) => approval.role === role && approval.status === "Pending" + ); + + if (approvalIndex === -1) { + return res.status(400).json({ message: "No pending approval found for this role." }); + } + + // Update the status of the approval to "Approved" + eventApproval.approvals[approvalIndex].status = "Approved"; + const nextRoleIndex = roleHierarchy.indexOf(role) + 1; + if (nextRoleIndex < roleHierarchy.length) { + const nextRole = roleHierarchy[nextRoleIndex]; + + sendEmail( + `${getEmailForRole(nextRole)}`, // Replace with actual email + `Event Approval Needed: ${eventApproval.eventName}`, + `The event "${eventApproval.eventName}" has been approved by ${role}. It is now pending your review and approval.` +      );  +  } + + // Save the updated event approval document + await eventApproval.save(); + + // Optionally, you can also send a notification email or take further actions here + res.status(200).json({ message: `${role} approved the application successfully.` }); + } catch (error) { + console.error("Error approving application:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// In your event.controller.js file + +export const handleApprovalStatus = async (req, res) => { + const { applicationId, role, status, comment } = req.body; + + try { + console.log("Received request:", { applicationId, role, status, comment }); // Debug log + + if (!role || !['Approved', 'Rejected', 'Query'].includes(status)) { + console.log("Invalid role or status"); // Debug log + return res.status(400).json({ message: "Invalid role or status." }); + } + + const eventApproval = await EventApproval.findById(applicationId); + + if (!eventApproval) { + console.log("Event approval not found"); // Debug log + return res.status(404).json({ message: "Event approval not found." }); + } + + const approvalIndex = eventApproval.approvals.findIndex( + (app) => app.role === role && app.status === "Pending" + ); + + if (approvalIndex === -1) { + console.log("No pending approval found for this role"); // Debug log + return res.status(400).json({ message: "No pending approval found for this role." }); + } + + eventApproval.approvals[approvalIndex].status = status; + eventApproval.approvals[approvalIndex].comment = comment || ""; + + if(status === "Rejected"){ + try { + await sendEmail( + eventApproval.email, + `Event Rejected: ${eventApproval.eventName}`, + `Your event "${eventApproval.eventName}" has been rejected by ${role}. Reason: ${comment || "No reason provided."}` + ); + console.log(`Rejection email sent to organizer: ${eventApproval.email}`); + } catch (emailError) { + console.error(`Failed to send rejection email to organizer:`, emailError.message); + } + } + else if(status === "Approved"){ + const nextRoleIndex = roleHierarchy.indexOf(role) + 1; + if (nextRoleIndex < roleHierarchy.length) { + const nextRole = roleHierarchy[nextRoleIndex]; + const nextRoleEmail = getEmailForRole(nextRole); + + if (nextRoleEmail) { + try { + await sendEmail( + nextRoleEmail, + `Event Approval Needed: ${eventApproval.eventName}`, + `The event "${eventApproval.eventName}" has been approved by ${role}. It is now pending your review and approval.` + ); + console.log(`Notification email sent to ${nextRole} at ${nextRoleEmail}`); + } catch (emailError) { + console.error(`Failed to send notification email to ${nextRole}:`, emailError.message); + } + } else { + console.error(`No email found for next role: ${nextRole}`); + } + } else if (nextRoleIndex === roleHierarchy.length) { + try { + await sendEmail( + eventApproval.email, + `Event Fully Approved: ${eventApproval.eventName}`, + `Congratulations! Your event "${eventApproval.eventName}" has been fully approved by all authorities. + Event Details: + - Event Name: ${eventApproval.eventName} + - Event Type: ${eventApproval.eventType} + - Date: ${new Date(eventApproval.startDate).toLocaleDateString()} to ${new Date(eventApproval.endDate).toLocaleDateString()} + - Venue: ${eventApproval.eventVenue} + - Organizer: ${eventApproval.nameOfTheOrganizer} + + Your event is now ready to proceed. Please ensure all arrangements are made as per the approved proposal. + + Best regards, + Event Approval Committee` + ); + console.log(`Final approval notification sent to organizer: ${eventApproval.email}`); + } catch (emailError) { + console.error(`Failed to send final approval email to organizer:`, emailError.message); + } + } +} + await eventApproval.save(); + + console.log("Application updated successfully:", { applicationId, status }); // Debug log + res.status(200).json({ message: `Application ${status} successfully.` }); + } catch (error) { + console.error("Error updating application status:", error); // Log full error + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Raise a query for an event application +export const raiseQuery = async (req, res) => { + const { applicationId, role, queryText } = req.body; + + try { + if (!applicationId || !role || !queryText) { + return res.status(400).json({ message: "Application ID, role, and query text are required." }); + } + + const eventApproval = await EventApproval.findById(applicationId); + if (!eventApproval) { + return res.status(404).json({ message: "Event approval not found." }); + } + + // Check if the role can raise queries (not club-secretary) + if (role === "club-secretary") { + return res.status(403).json({ message: "Club secretary cannot raise queries." }); + } + + // Check if there's a pending approval for this role + const approvalIndex = eventApproval.approvals.findIndex( + (app) => app.role === role && app.status === "Pending" + ); + + if (approvalIndex === -1) { + return res.status(400).json({ message: "No pending approval found for this role." }); + } + + // Update the approval status to "Query" + eventApproval.approvals[approvalIndex].status = "Query"; + eventApproval.approvals[approvalIndex].comment = `Query raised: ${queryText}`; + + // Add the query to the queries array + const newQuery = { + askerRole: role, + queryText, + responderEmail: eventApproval.email, + status: "Pending", + raisedAt: new Date(), + }; + + eventApproval.queries.push(newQuery); + await eventApproval.save(); + + // Send email notification to the organizer + try { + await sendEmail( + eventApproval.email, + `Query Raised for Event: ${eventApproval.eventName}`, + `A query has been raised for your event "${eventApproval.eventName}" by ${role}. + +Query: ${queryText} + +Please log into the application to respond to this query. + +Event Details: +- Event Name: ${eventApproval.eventName} +- Event Type: ${eventApproval.eventType} +- Date: ${new Date(eventApproval.startDate).toLocaleDateString()} to ${new Date(eventApproval.endDate).toLocaleDateString()} + +Please respond at your earliest convenience. + +Best regards, +Event Approval Committee` + ); + console.log(`Query notification email sent to organizer: ${eventApproval.email}`); + } catch (emailError) { + console.error(`Failed to send query notification email to organizer:`, emailError.message); + } + + res.status(200).json({ message: "Query raised successfully.", query: newQuery }); + } catch (error) { + console.error("Error raising query:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Get all queries for an event +export const getEventQueries = async (req, res) => { + const { eventId } = req.params; + + try { + const eventApproval = await EventApproval.findById(eventId, 'queries'); + if (!eventApproval) { + return res.status(404).json({ message: "Event not found." }); + } + + res.status(200).json({ queries: eventApproval.queries }); + } catch (error) { + console.error("Error fetching queries:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Reply to a query (only for club-secretary or organizer) +export const replyToQuery = async (req, res) => { + const { eventId, queryId, response, userRole, userEmail } = req.body; + + try { + if (!eventId || !queryId || !response) { + return res.status(400).json({ message: "Event ID, query ID, and response are required." }); + } + + const eventApproval = await EventApproval.findById(eventId); + if (!eventApproval) { + return res.status(404).json({ message: "Event not found." }); + } + + // Find the query + const queryIndex = eventApproval.queries.findIndex( + (query) => query.queryId.toString() === queryId && query.status === "Pending" + ); + + if (queryIndex === -1) { + return res.status(404).json({ message: "Query not found or already answered." }); + } + + const query = eventApproval.queries[queryIndex]; + + // Check if the user is authorized to reply (only club-secretary) + if (userRole !== "club-secretary") { + return res.status(403).json({ message: "Only club-secretary can reply to queries." }); + } + + // Update the query with response + eventApproval.queries[queryIndex].response = response; + eventApproval.queries[queryIndex].status = "Answered"; + eventApproval.queries[queryIndex].answeredAt = new Date(); + + // Reset the approval status back to Pending for the role that raised the query + const approvalIndex = eventApproval.approvals.findIndex( + (approval) => approval.role === query.askerRole + ); + + if (approvalIndex !== -1) { + eventApproval.approvals[approvalIndex].status = "Pending"; + eventApproval.approvals[approvalIndex].comment = ""; + } + + await eventApproval.save(); + + // Send email notification to the role that raised the query + const roleEmail = getEmailForRole(query.askerRole); + if (roleEmail) { + try { + await sendEmail( + roleEmail, + `Query Response Received: ${eventApproval.eventName}`, + `Your query for event "${eventApproval.eventName}" has been responded to. + +Original Query: ${query.queryText} +Response: ${response} + +You can now review the event application again and take appropriate action. + +Best regards, +Event Approval Committee` + ); + console.log(`Query response notification sent to ${query.askerRole} at ${roleEmail}`); + } catch (emailError) { + console.error(`Failed to send query response notification to ${query.askerRole}:`, emailError.message); + } + } else { + console.error(`No email found for role: ${query.askerRole}`); + } + + res.status(200).json({ message: "Query response submitted successfully." }); + } catch (error) { + console.error("Error replying to query:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Get all unique semesters and academic years for filtering +export const getSemesterOptions = async (req, res) => { + try { + const { role, category } = req.query; + + let matchQuery = {}; + + // Add role-based filtering if needed + if (role && role !== 'club-secretary') { + matchQuery[`approvals.role`] = role; + } + + // Add category filtering for general-secretary + if (role === 'general-secretary' && category) { + matchQuery.eventType = category; + } + + const semesterOptions = await EventApproval.aggregate([ + { $match: matchQuery }, + { + $group: { + _id: { + semester: "$semester", + academicYear: "$academicYear" + } + } + }, + { + $sort: { "_id.academicYear": -1, "_id.semester": 1 } + } + ]); + + const formattedOptions = semesterOptions.map(option => ({ + semester: option._id.semester, + academicYear: option._id.academicYear, + display: option._id.semester || `${option._id.academicYear} Academic Year` + })); + + res.status(200).json(formattedOptions); + } catch (error) { + console.error("Error fetching semester options:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Enhanced function to get approved applications with semester filtering and search +export const getApprovedApplicationsWithFilters = async (req, res) => { + const { role, category, semester, academicYear, search, page = 1, limit = 10 } = req.body; + + try { + if (!role) { + return res.status(400).json({ message: "Role is required." }); + } + + // Base query for approved applications + let query = { + "approvals": { + $elemMatch: { + role: role, + status: "Approved" + } + } + }; + + // Add semester filtering + if (semester) { + query.semester = semester; + } + + // Add academic year filtering + if (academicYear) { + query.academicYear = academicYear; + } + + // Add category filtering for general-secretary + if (role === "general-secretary" && category) { + query.eventType = category; + } + + // Add search functionality + if (search && search.trim()) { + const searchRegex = new RegExp(search.trim(), 'i'); + query.$or = [ + { eventName: searchRegex }, + { clubName: searchRegex }, + { nameOfTheOrganizer: searchRegex }, + { eventVenue: searchRegex }, + { eventDescription: searchRegex } + ]; + } + + // Filter out past events + const currentDate = new Date(); + query.endDate = { $gte: currentDate }; + + // Calculate pagination + const skip = (page - 1) * limit; + + // Execute query with pagination + const approvedApplications = await EventApproval.find(query) + .sort({ startDate: -1 }) // Sort by start date, newest first + .skip(skip) + .limit(parseInt(limit)); + + // Get total count for pagination + const totalCount = await EventApproval.countDocuments(query); + + // Group by semester for better organization + const groupedBySemester = approvedApplications.reduce((groups, app) => { + const semesterKey = app.semester || `${app.academicYear} Academic Year`; + if (!groups[semesterKey]) { + groups[semesterKey] = []; + } + groups[semesterKey].push(app); + return groups; + }, {}); + + res.status(200).json({ + applications: approvedApplications, + groupedBySemester, + pagination: { + currentPage: parseInt(page), + totalPages: Math.ceil(totalCount / limit), + totalCount, + hasNext: skip + approvedApplications.length < totalCount, + hasPrev: page > 1 + } + }); + } catch (error) { + console.error("Error fetching approved applications with filters:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +// Enhanced function to get pending applications with semester filtering and search +export const getPendingApprovalsWithFilters = async (req, res) => { + const { role, category, semester, academicYear, search, page = 1, limit = 10 } = req.body; + + try { + if (!role) { + return res.status(400).json({ message: "Role is required." }); + } + + const roleIndex = roleHierarchy.indexOf(role); + if (roleIndex === -1) { + return res.status(400).json({ message: "Invalid role." }); + } + + // Base query for pending applications + let query = { + "approvals.role": role, + }; + + // Add semester filtering + if (semester) { + query.semester = semester; + } + + // Add academic year filtering + if (academicYear) { + query.academicYear = academicYear; + } + + // Add category filtering for general-secretary + if (role === "general-secretary" && category) { + query.eventType = category; + } + + // Add search functionality + if (search && search.trim()) { + const searchRegex = new RegExp(search.trim(), 'i'); + query.$or = [ + { eventName: searchRegex }, + { clubName: searchRegex }, + { nameOfTheOrganizer: searchRegex }, + { eventVenue: searchRegex }, + { eventDescription: searchRegex } + ]; + } + + // Find all applications matching the base criteria + let pendingApprovals = await EventApproval.find(query); + + if (pendingApprovals.length === 0) { + return res.status(200).json({ + applications: [], + groupedBySemester: {}, + pagination: { + currentPage: 1, + totalPages: 0, + totalCount: 0, + hasNext: false, + hasPrev: false + } + }); + } + + // Filter for pending or query status + pendingApprovals = pendingApprovals.filter((approval) => { + const approvalStatus = approval.approvals.find( + (app) => app.role === role + ); + return approvalStatus && (approvalStatus.status === "Pending" || approvalStatus.status === "Query"); + }); + + // Ensure previous roles in the hierarchy are approved + pendingApprovals = pendingApprovals.filter((approval) => { + return roleHierarchy.slice(0, roleIndex).every((prevRole) => { + const prevApproval = approval.approvals.find((app) => app.role === prevRole); + return prevApproval && prevApproval.status === "Approved"; + }); + }); + + // Apply pagination + const totalCount = pendingApprovals.length; + const skip = (page - 1) * limit; + const paginatedApprovals = pendingApprovals + .sort((a, b) => new Date(a.startDate) - new Date(b.startDate)) + .slice(skip, skip + parseInt(limit)); + + // Group by semester + const groupedBySemester = paginatedApprovals.reduce((groups, app) => { + const semesterKey = app.semester || `${app.academicYear} Academic Year`; + if (!groups[semesterKey]) { + groups[semesterKey] = []; + } + groups[semesterKey].push(app); + return groups; + }, {}); + + res.status(200).json({ + applications: paginatedApprovals, + groupedBySemester, + pagination: { + currentPage: parseInt(page), + totalPages: Math.ceil(totalCount / limit), + totalCount, + hasNext: skip + paginatedApprovals.length < totalCount, + hasPrev: page > 1 + } + }); + } catch (error) { + console.error("Error fetching pending approvals with filters:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + + + +// Edit all event details (only allowed by club-secretary and only if approvals are still pending at general-secretary) +export const editEventDetails = async (req, res) => { + const { eventId, userID, updates } = req.body; + + try { + // Find the event + const event = await EventApproval.findById(eventId); + if (!event) { + return res.status(404).json({ message: "Event not found." }); + } + + const user = await User.findById(userID); + if (!user || user.role !== "club-secretary") { + return res.status(403).json({ message: "Only club-secretary can edit event details." }); + } + + if (event.userID.toString() !== userID.toString()) { + return res.status(403).json({ message: "You are not authorized to edit this event." }); + } + + + // List of fields that can be updated + const editableFields = [ + "eventName", "partOfGymkhanaCalendar", "eventType", "clubName", "startDate", "endDate", + "eventVenue", "sourceOfBudget", "estimatedBudget", "nameOfTheOrganizer", "designation", + "email", "phoneNumber", "requirements", "anyAdditionalAmenities", "eventDescription", + "internalParticipants", "externalParticipants", "listOfCollaboratingOrganizations" + ]; + + // Update only allowed fields + editableFields.forEach(field => { + if (updates[field] !== undefined) { + event[field] = updates[field]; + } + }); + + // If startDate changed, update semester/academicYear + if (updates.startDate) { + const semesterInfo = getSemesterInfo(updates.startDate); + event.semester = semesterInfo.semester; + event.academicYear = semesterInfo.academicYear; + } + + // Reset approvals after club-secretary to Pending + event.approvals = event.approvals.map(a => { + if (a.role !== "club-secretary") { + return { ...a, status: "Edited"}; + } + return a; + }); + + await event.save(); + + res.status(200).json({ message: "Event details updated and approval pipeline reset.", event }); + } catch (error) { + console.error("Error editing event details:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; + +export const closeEvent = async (req, res) => { + const { eventId, userID, closerName } = req.body; + + try { + // Find the user and check role + const user = await User.findById(userID); + if (!user || !["ARSW", "associate-dean", "dean"].includes(user.role)) { + return res.status(403).json({ message: "Only ARSW, associate-dean or dean can close events." }); + } + + // Find the event + const event = await EventApproval.findById(eventId); + if (!event) { + return res.status(404).json({ message: "Event not found." }); + } + + // Set status to Closed and record who closed it + event.status = "Closed"; + event.closedBy = closerName || user.name || "Unknown"; + + await event.save(); + + res.status(200).json({ message: "Event closed successfully.", event }); + } catch (error) { + console.error("Error closing event:", error); + res.status(500).json({ message: "Internal server error." }); + } +}; \ No newline at end of file diff --git a/backend/controller/leave.controller.js b/backend/controller/leave.controller.js deleted file mode 100644 index db492b3..0000000 --- a/backend/controller/leave.controller.js +++ /dev/null @@ -1,225 +0,0 @@ -import LeaveApplication from "../models/leave.model.js"; -import User from "../models/user.model.js"; -import nodemailer from "nodemailer"; -import dotenv from "dotenv"; - -dotenv.config(); - -// Configure nodemailer -const transporter = nodemailer.createTransport({ - service: 'gmail', - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASS - } -}); - - -const sendEmail = (to, subject, text) => { - const mailOptions = { - from: process.env.EMAIL_USER, - to, - subject, - text - }; - - console.log('Attempting to send email to:', to); - - transporter.sendMail(mailOptions, (error, info) => { - if (error) { - console.error('Error sending email:', error); - } else { - console.log('Email sent:', info.response); - } - }); -}; - - -// Apply for Leave -export const applyForLeave = async (req, res) => { - try { - const { placeOfVisit, reason, dateOfLeaving, arrivalDate, emergencyContact } = req.body; - const userID = req.body.userID; // Ensure you get this from local storage - - // Fetch user details to check if required fields are filled - const user = await User.findById(userID); - - // Check if required user details are filled - if (!user.phnumber || !user.roomNumber || !user.year || !user.course || !user.hostel) { - return res.status(400).json({ message: "Please complete your profile with phone number, room number, year, and course before applying for leave." }); - } - - // Check for pending leave applications for the user - const existingApplication = await LeaveApplication.findOne({ - userID, - status: 'Pending' - }); - - if (existingApplication) { - return res.status(400).json({ message: "You already have a pending leave application." }); - } - - // If no pending leave application, proceed to create a new leave application - const newApplication = new LeaveApplication({ - userID, - placeOfVisit, - reason, - dateOfLeaving, - arrivalDate, - emergencyContact - }); - - await User.findByIdAndUpdate(userID, { leaveApplication: newApplication._id }); - - await newApplication.save(); - res.status(201).json({ - message: "Leave application submitted successfully.", - application: newApplication - }); - } catch (error) { - console.log("error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -// Get Leave Status for a User -export const getLeaveStatus = async (req, res) => { - try { - const { userID } = req.body; // Ensure you're getting userID correctly - - // Use findOne to get only one leave application for the user - const leaveApplication = await LeaveApplication.findOne({ userID }); - - if (!leaveApplication) { - return res.status(404).json({ message: "No leave applications found" }); - } - - res.status(200).json(leaveApplication); // Send the single leave application as a JSON response - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - - - -// Warden approval -export const updateLeaveStatus = async (req, res) => { - try { - const { applicationId } = req.params; // Get applicationId from request parameters takes leave id - const { status } = req.body; // Get status from request body - - // Validate the status - if (!['Pending', 'Approved', 'Rejected'].includes(status)) { - return res.status(400).json({ message: "Invalid status. Must be either 'Pending', 'Approved', or 'Rejected'." }); - } - - // Find the leave application and update the status - const updatedApplication = await LeaveApplication.findByIdAndUpdate( - applicationId, - { status }, - { new: true } // Return the updated document - ); - - if (!updatedApplication) { - return res.status(404).json({ message: "Leave application not found" }); - } - - // If status is approved, send email to the user - if (status === 'Approved') { - const user = await User.findById(updatedApplication.userID); - const subject = "Your Leave Application has been Approved"; - const text = `Dear ${user.name},\n\nYour leave application for ${updatedApplication.placeOfVisit} has been approved.\n\nBest regards,\nYour College`; - await sendEmail(user.email, subject, text); - } - - res.status(200).json({ - message: "Leave application status updated successfully.", - application: updatedApplication - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -// Leave extension -export const updateLeaveExtension = async (req, res) => { - try { - const { applicationId } = req.params; - const { newArrivalDate } = req.body; - - const leaveApplication = await LeaveApplication.findById(applicationId); - - if (!leaveApplication) { - return res.status(404).json({ message: "Leave application not found" }); - } - - // Update the arrival date and mark the status as 'Extension' - leaveApplication.arrivalDate = newArrivalDate; - leaveApplication.status = 'Extension'; - - await leaveApplication.save(); - - res.status(200).json({ - message: "Leave extension requested successfully.", - application: leaveApplication - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -// All leave applications for warden -export const getAllLeaveApplications = async (req, res) => { - try { - const applications = await LeaveApplication.find().populate("userID", "name rollNumber email"); - res.status(200).json(applications); - } catch (error) { - console.error("Error fetching leave applications:", error); - res.status(500).json({ message: "Server error" }); - } -}; - -// Scan leave application -export const scanLeaveApplication = async (req, res) => { - try { - const { applicationId } = req.params; // Get applicationId from request parameters - const leaveApplication = await LeaveApplication.findById(applicationId); - - if (!leaveApplication) { - return res.status(404).json({ message: "Leave application not found" }); - } - - // Update the scanned field with the current date and time - leaveApplication.scanned = new Date(); - await leaveApplication.save(); - - res.status(200).json({ - message: "Leave application scanned successfully.", - application: leaveApplication - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -export const deleteLeaveApplication = async (req, res) => { - try { - const { id } = req.params; // Get applicationId from request parameters - const leaveApplication = await LeaveApplication.findByIdAndDelete(id); - - if (!leaveApplication) { - return res.status(404).json({ message: "Leave application not found" }); - } - - res.status(200).json({ - message: "Leave application deleted successfully.", - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; \ No newline at end of file diff --git a/backend/controller/outing.controller.js b/backend/controller/outing.controller.js deleted file mode 100644 index 292524e..0000000 --- a/backend/controller/outing.controller.js +++ /dev/null @@ -1,123 +0,0 @@ -import OutingRequest from "../models/outing.model.js"; -import User from "../models/user.model.js"; - -// Apply for Outing -export const applyForOuting = async (req, res) => { - try { - const { placeOfVisit, reason, emergencyContact } = req.body; - const userID = req.body.userID; // will be fetched from local storage on frontend - - // Check if the user already has an approved outing request - const existingApplication = await OutingRequest.findOne({ - userID, - status: 'Approved' - }); - - if (existingApplication) { - return res.status(400).json({ message: "You already have an existing outing application." }); - } - - // If no existing approved application, create a new outing request - const newApplication = new OutingRequest({ - userID, - placeOfVisit, - reason, - outTime: new Date(), // Set outTime to the current date and time - inTime: "", // You can also set a default value for inTime if needed - emergencyContact - }); - - // Save the outing request ID to the user's document - await User.findByIdAndUpdate(userID, { outingRequest: newApplication._id }); - - await newApplication.save(); - res.status(201).json({ - message: "Outing application submitted successfully.", - application: newApplication - }); - } catch (error) { - console.log("error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -// Get Outing Status -export const getOutingStatus = async (req, res) => { - try { - const { userID } = req.body; // Ensure you're getting userID correctly - - // Use findOne to get only one outing request for the user - const outingRequest = await OutingRequest.findOne({ userID }) - - if (!outingRequest) { - return res.status(404).json({ message: "No outing application found." }); - } - - // Return the single outing request - res.status(200).json({ - outingRequest - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error." }); - } -}; - - -export const getAllOutings = async (req, res) => { - try { - // Fetch all outing requests from the database - const allOutings = await OutingRequest.find(); - - if (allOutings.length === 0) { - return res.status(404).json({ message: "No outing applications found." }); - } - - // Return the list of all outing requests - res.status(200).json(allOutings); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error." }); - } -}; - -export const scanOutingApplication = async (req, res) => { - try { - const { applicationId } = req.params; // Get applicationId from request parameters - const outingApplication = await OutingRequest.findById(applicationId); - - if (!outingApplication) { - return res.status(404).json({ message: "Outing application not found" }); - } - - // Update the scanned field with the current date and time - outingApplication.scanned = new Date(); - await outingApplication.save(); - - res.status(200).json({ - message: "Outing application scanned successfully.", - application: outingApplication - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; - -export const deleteOutingRequest = async (req, res) => { - try { - const { applicationId } = req.params; // Get applicationId from request parameters - const outingRequest = await OutingRequest.findByIdAndDelete(applicationId); - - if (!outingRequest) { - return res.status(404).json({ message: "outing request not found" }); - } - - res.status(200).json({ - message: "outing request deleted successfully.", - }); - } catch (error) { - console.log("Error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } -}; \ No newline at end of file diff --git a/backend/controller/user.controller.js b/backend/controller/user.controller.js index fe5553f..1262ce5 100644 --- a/backend/controller/user.controller.js +++ b/backend/controller/user.controller.js @@ -1,93 +1,130 @@ import User from "../models/user.model.js"; import bcryptjs from 'bcryptjs'; import jwt from "jsonwebtoken"; +import { oauth2Client } from "../googleClient.js"; +import axios from "axios"; -export const signup = async (req, res) => { - try { - const { email, password, rollNumber } = req.body; +// export const signup = async (req, res) => { +// try { +// const { email, password, category } = req.body; - // Check if the user already exists - const existingUser = await User.findOne({ email }); - if (existingUser) { - return res.status(400).json({ message: "User already exists" }); - } +// // Check if the user already exists +// const existingUser = await User.findOne({ email }); +// if (existingUser) { +// return res.status(400).json({ message: "User already exists" }); +// } + +// // Hash the password and create a new user +// const hashPassword = await bcryptjs.hash(password, 8); +// const newUser = new User({ +// email, +// password: hashPassword, +// name: "", +// category, +// eventApproval: "", +// phnumber: "", +// }); + +// await newUser.save(); +// const token = jwt.sign( +// { userID: newUser._id, role: newUser.role }, +// process.env.JWT_SECRET, +// { expiresIn: "1h" } // Set your preferred token expiry time +// ); +// res.status(201).json({ +// message: "User created successfully", +// token, +// user: { _id: newUser._id, name: newUser.name, email: newUser.email, role: newUser.role, category: newUser.category }, +// }); + +// } catch (error) { +// console.log("error:", error.message); +// res.status(500).json({ message: "Internal server error" }); +// } +// }; + +// //login function + +// export const login = async (req, res) => { +// try { +// const { email, password } = req.body; + +// const user = await User.findOne({ email }); +// if (!user) { +// return res.status(400).json({ message: "User not found" }); +// } + +// const isMatch = await bcryptjs.compare(password, user.password); +// if (!isMatch) { +// return res.status(400).json({ message: "Invalid credentials" }); +// } + +// // Create a JWT token +// const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { +// expiresIn: '1h', +// }); + +// res.status(200).json({ +// message: "Login successful", +// token, +// user: { _id: user._id, name: user.name, email: user.email, role: user.role }, +// }); +// } catch (error) { +// console.log("error:", error.message); +// res.status(500).json({ message: "Internal server error" }); +// } +// }; + +//Google login function - // Hash the password and create a new user - const hashPassword = await bcryptjs.hash(password, 8); - const newUser = new User({ - email, - password: hashPassword, - name: "", - rollNumber, - leaveApplication: "", - outingRequest: "", - roomNumber:"", - branch:"", - year: "", - course: "" +export const googleLogin = async (req, res) => { + const { token } = req.body; // JWT sent from the frontend + + try { + // Verify the JWT + const ticket = await oauth2Client.verifyIdToken({ + idToken: token, + audience: process.env.GOOGLE_CLIENT_ID, // Replace with your client ID }); - await newUser.save(); - const token = jwt.sign( - { userID: newUser._id, role: newUser.role }, + const payload = ticket.getPayload(); + const { email, name, picture } = payload; + + // Find or create a user in the database + let user = await User.findOne({ email }); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + // Generate a JWT token for your application + const appToken = jwt.sign( + { userID: user._id, role: user.role }, process.env.JWT_SECRET, - { expiresIn: "1h" } // Set your preferred token expiry time + { expiresIn: "1h" } ); - res.status(201).json({ - message: "User created successfully", - token, - user: { _id: newUser._id, name: newUser.name, email: newUser.email, role: newUser.role }, + + res.status(200).json({ + message: "success", + token: appToken, + user, }); - } catch (error) { - console.log("error:", error.message); - res.status(500).json({ message: "Internal server error" }); + console.error("Google Login Error:", error); + res.status(500).json({ message: "Google Login failed" }); } }; -//login function - -export const login = async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - if (!user) { - return res.status(400).json({ message: "User not found" }); - } - - const isMatch = await bcryptjs.compare(password, user.password); - if (!isMatch) { - return res.status(400).json({ message: "Invalid credentials" }); - } - - // Create a JWT token - const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { - expiresIn: '1h', - }); - - res.status(200).json({ - message: "Login successful", - token, - user: { _id: user._id, name: user.name, email: user.email, role: user.role }, - }); - } catch (error) { - console.log("error:", error.message); - res.status(500).json({ message: "Internal server error" }); - } - }; - export const getUserDetails = async (req, res) => { try { - const { userId, rollNumber } = req.body; // Destructure both userId and rollNumber from request body + const { userId, email } = req.body; // Destructure both userId and rollNumber from request body let user; // If userId is provided, find by userId; otherwise, find by rollNumber if (userId) { - user = await User.findById(userId, "leaveApplication outingRequest name rollNumber email phnumber hostel roomNumber course branch year"); - } else if (rollNumber) { - user = await User.findOne({ rollNumber }, "name rollNumber email"); // Find by rollNumber + user = await User.findById(userId, "eventApproval name email phnumber category"); + } else if (email) { + user = await User.findOne({ email }, "name eventApproval phnumber category"); } // Check if user was found @@ -108,7 +145,7 @@ export const login = async (req, res) => { const { userId, ...updates } = req.body; // Destructure userId and the rest as updates // Ensure only permitted fields can be updated - const allowedFields = ["name", "roomNumber", "branch", "year", "course", "hostel", "phnumber"]; + const allowedFields = ["name", "category", "phnumber"]; // Retrieve the existing user data const user = await User.findById(userId); diff --git a/backend/googleClient.js b/backend/googleClient.js new file mode 100644 index 0000000..2ee6bcb --- /dev/null +++ b/backend/googleClient.js @@ -0,0 +1,13 @@ +import { google } from 'googleapis' +import dotenv from "dotenv" + +dotenv.config(); + +const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID; +const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET; + +export const oauth2Client = new google.auth.OAuth2( + GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET, + 'postmessage' +); \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index ebf2059..66fb856 100644 --- a/backend/index.js +++ b/backend/index.js @@ -4,8 +4,8 @@ import mongoose from "mongoose" import cors from "cors" import userRoute from "./route/user.route.js" -import leaveRoute from "./route/leave.route.js" -import outRoute from "./route/outing.route.js" +import eventRoute from "./route/event.route.js" + const app = express() @@ -17,18 +17,15 @@ app.use(express.json()); const PORT= process.env.PORT || 4000; const URI = process.env.MongoDBURI; -mongoose.connect(URI, { - useNewUrlParser: true, - useUnifiedTopology: true -}).then(() => { +mongoose.connect(URI).then(() => { console.log("Connected to MongoDB"); }).catch((error) => { console.log("error:", error); }); -app.use("/user", userRoute); -app.use("/leave", leaveRoute); -app.use("/out", outRoute); + app.use("/user", userRoute); + app.use("/event", eventRoute); + diff --git a/backend/models/event.model.js b/backend/models/event.model.js new file mode 100644 index 0000000..e000cc7 --- /dev/null +++ b/backend/models/event.model.js @@ -0,0 +1,93 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +const eventApprovalSchema = new Schema( + { + userID: { type: String, required: true }, + eventName: { type: String, required: true }, + partOfGymkhanaCalendar: { type: String, required: true }, + eventType: { type: String }, // Optional field for tracking event type + clubName: { type: String, required: true }, + startDate: { type: Date, required: true }, + endDate: { type: Date, required: true }, + semester: { type: String, required: false }, // Semester field (e.g., "Fall 2024", "Spring 2025") + academicYear: { type: String, required: false }, // Academic year (e.g., "2024-2025") + eventVenue: { type: String, required: true }, + sourceOfBudget: { type: String, required: true }, + estimatedBudget: { type: Number, required: true }, + nameOfTheOrganizer: { type: String, required: true }, + designation: { type: String, required: true }, + email: { type: String, required: true }, + phoneNumber: { type: String, required: true }, + requirements: { type: [String], required: false }, + anyAdditionalAmenities: { type: String }, + eventDescription: { type: String, required: true }, + internalParticipants: { type: Number, required: true }, + externalParticipants: { type: Number, required: true }, + listOfCollaboratingOrganizations: { type: String, default: "N/A" }, + approvals: [ + { + role: { + type: String, + enum: [ + "club-secretary", + "general-secretary", + "treasurer", + "president", + "faculty-in-charge", + "associate-dean", + ], + required: true, + }, + status: { + type: String, + enum: [ + "Pending", + "Approved", + "Rejected", + "Query", + "Edited", + "Closed", + ], + default: "Pending", + }, + comment: { type: String, required: false }, // Optional comment for feedback + }, + ], + queries: [ + { + queryId: { + type: mongoose.Schema.Types.ObjectId, + default: () => new mongoose.Types.ObjectId(), + }, + askerRole: { + type: String, + enum: [ + "general-secretary", + "treasurer", + "president", + "faculty-in-charge", + "associate-dean", + ], + required: true, + }, + queryText: { type: String, required: true }, + responderEmail: { type: String, required: true }, // Email of the organizer who should respond + response: { type: String, required: false }, + status: { + type: String, + enum: ["Pending", "Answered"], + default: "Pending", + }, + raisedAt: { type: Date, default: Date.now }, + answeredAt: { type: Date, required: false }, + }, + ], + closedBy: { type: String, required: false }, // Name of the person who closed the event + }, + { timestamps: true } +); + +const EventApproval = mongoose.model("EventApproval", eventApprovalSchema); +export default EventApproval; diff --git a/backend/models/leave.model.js b/backend/models/leave.model.js deleted file mode 100644 index c415606..0000000 --- a/backend/models/leave.model.js +++ /dev/null @@ -1,19 +0,0 @@ -import mongoose from "mongoose"; - -const Schema = mongoose.Schema; - -const leaveApplicationSchema = new Schema({ - userID: { type: String, required: true }, - placeOfVisit: { type: String, required: true }, - reason: { type: String, required: true }, - proofOfTravel: { type: String, required: false }, - dateOfLeaving: { type: Date, required: true }, - arrivalDate: { type: Date, required: true }, - emergencyContact: { type: String, required: true }, - status: { type: String, default: 'Pending', enum: ['Pending', 'Approved', 'Rejected', 'Extension'] }, // Default status - scanned: { type: Date, default: null }, -}, { -}); - -const LeaveApplication = mongoose.model('LeaveApplication', leaveApplicationSchema); -export default LeaveApplication; diff --git a/backend/models/outing.model.js b/backend/models/outing.model.js deleted file mode 100644 index f2b97ab..0000000 --- a/backend/models/outing.model.js +++ /dev/null @@ -1,18 +0,0 @@ -import mongoose from "mongoose"; - -const Schema = mongoose.Schema; - -const outingRequestSchema = new Schema({ - userID: { type: String, required: true }, - placeOfVisit: { type: String, required: true }, - reason: { type: String, required: true }, - outTime: { type: Date, required: false }, - inTime: { type: Date, required: false }, - emergencyContact: {type: String, required: true}, - status: { type: String, default: 'Approved'}, - scanned: { type: Date, default: null } -}, -); - -const OutingRequest = mongoose.model('OutingRequest', outingRequestSchema); -export default OutingRequest; \ No newline at end of file diff --git a/backend/models/user.model.js b/backend/models/user.model.js index fc6fe5b..c0b0e12 100644 --- a/backend/models/user.model.js +++ b/backend/models/user.model.js @@ -16,18 +16,21 @@ const userSchema = new Schema({ message: 'Email must have the suffix "iitdh.ac.in"', }, }, - password: {type: String, required: true}, + password: {type: String, required: false}, name: { type: String, required: false }, - rollNumber: { type: String, required: false }, - role: {type: String, default: "student",enum: ["student", "warden","security"]}, - phnumber: {type:String, default: "", required:false}, - leaveApplication: {type: String, default: "",required: false}, - outingRequest: {type: String, default: "",required: false}, - hostel: {type: String, default: "", required:false}, - roomNumber: {type: String, required: false}, - branch: {type: String, required: false}, - year: {type: String, required: false}, - course: {type: String, required: false} + role: {type: String, default: "club-secretary",enum: ["club-secretary", "general-secretary", "treasurer", "vice-president", "ARSW", "associate-dean", "Dean-SW"]}, + type: { + type: String, + enum: ["Technical", "Cultural", "Sports"], + required: function () { + return this.role === "club-secretary"; + }, + default: undefined + }, + eventApproval: {type: String, default: "",required: false}, + category: {type: String, default: "",required: false}, + phnumber: {type: String, default: "", required:false}, + image: {type: String, required: false}, }, ); diff --git a/backend/package-lock.json b/backend/package-lock.json index 3965836..0b37cff 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -5,10 +5,12 @@ "packages": { "": { "dependencies": { + "axios": "^1.7.9", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", + "googleapis": "^144.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.7.3", "nodemailer": "^6.9.16", @@ -48,6 +50,14 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -65,16 +75,58 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "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==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcryptjs": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -130,9 +182,9 @@ } }, "node_modules/bson": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz", - "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", + "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", "engines": { "node": ">=16.20.1" } @@ -191,6 +243,17 @@ "fsevents": "~2.3.2" } }, + "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==", + "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", @@ -264,6 +327,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -346,9 +417,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -369,7 +440,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -384,8 +455,17 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -414,6 +494,38 @@ "node": ">= 0.8" } }, + "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" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -451,6 +563,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -480,6 +607,81 @@ "node": ">= 6" } }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -491,6 +693,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -558,6 +791,39 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/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==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -625,6 +891,25 @@ "node": ">=0.12.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -784,12 +1069,12 @@ } }, "node_modules/mongodb": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", - "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", "dependencies": { - "@mongodb-js/saslprep": "^1.1.5", - "bson": "^6.7.0", + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -797,7 +1082,7 @@ }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.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", @@ -838,13 +1123,13 @@ } }, "node_modules/mongoose": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.3.tgz", - "integrity": "sha512-Xl6+dzU5ZpEcDoJ8/AyrIdAwTY099QwpolvV73PIytpK13XqwllLq/9XeVzzLEQgmyvwBVGVgjmMrKbuezxrIA==", + "version": "8.9.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz", + "integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==", "dependencies": { - "bson": "^6.7.0", + "bson": "^6.10.1", "kareem": "2.6.3", - "mongodb": "6.9.0", + "mongodb": "~6.12.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -916,6 +1201,44 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/nodemailer": { "version": "6.9.16", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", @@ -1019,9 +1342,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1046,6 +1369,11 @@ "node": ">= 0.10" } }, + "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==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -1335,6 +1663,11 @@ "node": ">= 0.8" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1343,6 +1676,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index db07454..b9995bf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,9 +1,11 @@ { "dependencies": { + "axios": "^1.7.9", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", + "googleapis": "^144.0.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.7.3", "nodemailer": "^6.9.16", diff --git a/backend/route/event.route.js b/backend/route/event.route.js new file mode 100644 index 0000000..33c24ed --- /dev/null +++ b/backend/route/event.route.js @@ -0,0 +1,68 @@ +import express from "express"; +import { + applyForEventApproval, + getUserEvents, + approveApplication, + getApprovedApplications, + editEventDetails, + getPendingApprovals, + getRejectedApplications, + getEventById, + handleApprovalStatus, // Import the updated controller + raiseQuery, + getEventQueries, + replyToQuery, + getSemesterOptions, + getApprovedApplicationsWithFilters, + closeEvent, + getPendingApprovalsWithFilters, +} from "../controller/event.controller.js"; + +const router = express.Router(); + +// Apply for event approval (POST) +router.post("/apply", applyForEventApproval); + +// Get all events for a user (POST) +router.post("/user-events", getUserEvents); + +// Get all pending event applications (POST) +router.post("/pending", getPendingApprovals); + +// Get all approved event applications (POST) +router.post("/approved", getApprovedApplications); + +// Get all rejected event applications (POST) +router.post("/rejected", getRejectedApplications); + +// Approve or reject an event application based on the status (PATCH) +router.patch("/:applicationId/status", handleApprovalStatus); + +// Get event details by ID (GET) +router.get("/:id", getEventById); + +// Raise a query for an event application (POST) +router.post("/raise-query", raiseQuery); + +// Get all queries for an event (GET) +router.get("/:eventId/queries", getEventQueries); + +// Reply to a query (POST) +router.post("/reply-query", replyToQuery); + +//Close an event (PATCH) +router.patch("/close", closeEvent); + +// Edit all event details (PATCH) +router.patch("/edit", editEventDetails); + +// Get semester options for filtering (GET) +router.get("/semesters/options", getSemesterOptions); + +// Get approved applications with filters and search (POST) +router.post("/approved/filtered", getApprovedApplicationsWithFilters); + +// Get pending applications with filters and search (POST) +router.post("/pending/filtered", getPendingApprovalsWithFilters); + +export default router; \ No newline at end of file diff --git a/backend/route/leave.route.js b/backend/route/leave.route.js deleted file mode 100644 index f5083e1..0000000 --- a/backend/route/leave.route.js +++ /dev/null @@ -1,17 +0,0 @@ -import express from "express"; -import { applyForLeave, getAllLeaveApplications, getLeaveStatus, updateLeaveExtension, updateLeaveStatus,scanLeaveApplication, deleteLeaveApplication } from '../controller/leave.controller.js' - - - -const router = express.Router(); - -router.post("/apply", applyForLeave); -router.post("/status",getLeaveStatus); -router.put("/update/:applicationId", updateLeaveStatus); -router.put("/extension/:applicationId", updateLeaveExtension); -router.get("/all", getAllLeaveApplications); -router.patch('/scan/:applicationId', scanLeaveApplication); -router.delete('/delete/:id', deleteLeaveApplication); - - -export default router; diff --git a/backend/route/outing.route.js b/backend/route/outing.route.js deleted file mode 100644 index 372135c..0000000 --- a/backend/route/outing.route.js +++ /dev/null @@ -1,19 +0,0 @@ -// outing.routes.js - -import express from "express"; -import { applyForOuting, getOutingStatus, getAllOutings,scanOutingApplication, deleteOutingRequest } from "../controller/outing.controller.js"; -import { deleteModel } from "mongoose"; - -const router = express.Router(); - -// Route to apply for an outing -router.post("/outapply", applyForOuting); - -// Route to get the status of outing applications for a specific user -router.post('/status', getOutingStatus); - -// Route to get all outing applications -router.get('/all', getAllOutings); // New route for getting all outings -router.patch('/scan/:applicationId', scanOutingApplication); -router.delete('/delete/:applicationId', deleteOutingRequest); -export default router; diff --git a/backend/route/user.route.js b/backend/route/user.route.js index 5f12be6..d0c536b 100644 --- a/backend/route/user.route.js +++ b/backend/route/user.route.js @@ -1,11 +1,11 @@ import express from "express"; -import { editUserDetails, getUserDetails, login, signup } from "../controller/user.controller.js"; +import { editUserDetails, getUserDetails, googleLogin } from "../controller/user.controller.js"; + const router = express.Router(); -router.post("/signup", signup); -router.post("/login", login); +router.post("/google-login", googleLogin); router.post("/details", getUserDetails); router.put("/edit", editUserDetails); - +router.get("/google", googleLogin) export default router; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..48eeadd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: event-approval-backend + ports: + - "4001:5000" + environment: + - NODE_ENV=production + volumes: + - ./backend:/app + - /app/node_modules + depends_on: + - mongo + networks: + - app-network + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: event-approval-frontend + ports: + - "5173:80" + depends_on: + - backend + networks: + - app-network + + mongo: + image: mongo:5.0 + container_name: event-approval-mongo + ports: + - "27017:27017" + volumes: + - mongo_data:/data/db + environment: + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=password + networks: + - app-network + +networks: + app-network: + driver: bridge + +volumes: + mongo_data: \ No newline at end of file diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..d64256b --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,13 @@ +node_modules +npm-debug.log +.env +.git +.gitignore +README.md +Dockerfile +.dockerignore +coverage +.nyc_output +dist +build +.log \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..c9a8ae3 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,37 @@ +# Multi-stage build for React app + +# Stage 1: Build the React app +FROM node:22-alpine AS build + +# Set working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Set environment variables for production build +ENV NODE_ENV=production + +# Build the app with more memory and debugging +RUN npm run build 2>&1 || (echo "Build failed, checking files:" && ls -la && exit 1) + +# Stage 2: Serve the built app +FROM nginx:alpine + +# Copy built files from previous stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy custom nginx configuration if needed (optional) +# COPY nginx.conf /etc/nginx/nginx.conf + +# Expose port 80 +EXPOSE 5173 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 0c589ec..731472d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,8 @@ - Vite + React + Event Permissions +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a34aadc..c15f273 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,23 +1,26 @@ { - "name": "scs2-0", + "name": "event-approvals", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "scs2-0", + "name": "event-approvals", "version": "0.0.0", "dependencies": { + "@react-oauth/google": "^0.12.1", "@zxing/library": "^0.21.3", "axios": "^1.7.7", "bootstrap": "^5.3.3", "chart.js": "^4.4.5", + "jspdf": "^3.0.3", "jwt-decode": "^4.0.0", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0", "react-modal": "^3.16.1", "react-router-dom": "^6.27.0" }, @@ -31,65 +34,48 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.13", "globals": "^15.11.0", - "vite": "^5.4.9" - } - }, - "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" + "vite": "^7.1.6" } }, "node_modules/@babel/code-frame": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz", - "integrity": "sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.0.tgz", - "integrity": "sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -105,16 +91,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.0.tgz", - "integrity": "sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.0", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -122,14 +107,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -138,30 +122,37 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "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==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -171,67 +162,61 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", - "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -241,13 +226,12 @@ } }, "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -257,13 +241,12 @@ } }, "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==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -273,472 +256,479 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" }, "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.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", "cpu": [ "mips64el" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, - "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -757,7 +747,6 @@ "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" }, @@ -766,23 +755,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.2.tgz", - "integrity": "sha512-2WwyTYNVaMNUWPZTOJdkax9iqTdirrApgTbk+Qoq5EPX6myqZvG8QGFRgdKmkjKVG6/G/a565vpPauHk0+hpBA==", + "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.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -790,22 +777,32 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "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.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "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", @@ -829,7 +826,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -838,32 +834,33 @@ } }, "node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "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.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", - "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, - "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -871,24 +868,22 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", - "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "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.5", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", - "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.0", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" @@ -909,11 +904,10 @@ } }, "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==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -923,18 +917,23 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -942,34 +941,21 @@ "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" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true }, "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==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1006,6 +992,15 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@react-oauth/google": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz", + "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@remix-run/router": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", @@ -1057,225 +1052,280 @@ "react": ">=16.14.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", + "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", + "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", + "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", + "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", + "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", + "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", + "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", + "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", + "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", + "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", + "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", + "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", + "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", + "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", + "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", + "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", + "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", + "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", + "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", + "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", + "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1336,11 +1386,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1349,12 +1398,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", "license": "MIT" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", @@ -1384,6 +1444,12 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", @@ -1391,23 +1457,23 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", - "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@zxing/library": { @@ -1431,11 +1497,10 @@ "optional": true }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1448,7 +1513,6 @@ "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" } @@ -1458,7 +1522,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", @@ -1490,8 +1553,7 @@ "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" + "dev": true }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", @@ -1652,12 +1714,12 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -1668,6 +1730,24 @@ "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==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", + "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/bootstrap": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", @@ -1688,20 +1768,19 @@ } }, "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==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", "dev": true, "funding": [ { @@ -1717,12 +1796,12 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -1751,20 +1830,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "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.30001671", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001671.tgz", - "integrity": "sha512-jocyVaSSfXg2faluE6hrWkMgDOiULBMca4QLtDT39hw1YxaIPHWc1CcTCKkPmHgGH6tKji6ZNbMSmUAvENf2/A==", + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", "dev": true, "funding": [ { @@ -1779,8 +1869,32 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true }, "node_modules/chalk": { "version": "4.1.2", @@ -1859,15 +1973,24 @@ "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" + "dev": true + }, + "node_modules/core-js": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "hasInstallScript": true, + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "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", @@ -1877,6 +2000,15 @@ "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==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2038,12 +2170,33 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "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==", + "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.47", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.47.tgz", - "integrity": "sha512-zS5Yer0MOYw4rtK2iq43cJagHZ8sXN0jDHDKzB+86gSBSAI4v07S97mcq+Gs2vclAxSh1j7vOAHxSVgduiiuVQ==", - "dev": true, - "license": "ISC" + "version": "1.5.222", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz", + "integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==", + "dev": true }, "node_modules/es-abstract": { "version": "1.23.3", @@ -2107,14 +2260,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -2123,7 +2271,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2156,11 +2303,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "license": "MIT", + "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==", "dependencies": { "es-errors": "^1.3.0" }, @@ -2169,15 +2314,14 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -2212,42 +2356,44 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", "dev": true, "hasInstallScript": true, - "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" } }, "node_modules/escalade": { @@ -2255,7 +2401,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -2274,32 +2419,32 @@ } }, "node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", - "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.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.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2313,8 +2458,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -2391,11 +2535,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2408,11 +2551,10 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2421,15 +2563,14 @@ } }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2456,7 +2597,6 @@ "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" }, @@ -2493,15 +2633,13 @@ "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" + "dev": true }, "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" + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -2510,6 +2648,38 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2591,12 +2761,14 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -2609,7 +2781,6 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -2622,7 +2793,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2662,23 +2832,25 @@ "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.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "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", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "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" @@ -2687,6 +2859,18 @@ "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==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -2758,13 +2942,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2817,11 +2999,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -2833,7 +3013,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -2849,7 +3028,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2858,22 +3036,33 @@ "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==", + "optional": true, + "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.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "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" @@ -2919,6 +3108,11 @@ "loose-envify": "^1.0.0" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==" + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -3316,7 +3510,6 @@ "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" }, @@ -3325,11 +3518,10 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "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" }, @@ -3348,8 +3540,7 @@ "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" + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -3363,7 +3554,6 @@ "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" }, @@ -3371,6 +3561,22 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", + "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", + "dependencies": { + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -3459,11 +3665,18 @@ "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==", + "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", @@ -3504,9 +3717,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3514,7 +3727,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3530,11 +3742,10 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -3689,12 +3900,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "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" }, @@ -3729,12 +3944,29 @@ "dev": true, "license": "MIT" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "license": "ISC" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -3747,9 +3979,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -3765,10 +3997,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -3819,11 +4050,19 @@ "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/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -3905,6 +4144,15 @@ "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-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -3936,11 +4184,10 @@ } }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "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" } @@ -4015,12 +4262,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", @@ -4063,19 +4304,26 @@ "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/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.50.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", + "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", "dev": true, - "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -4085,22 +4333,27 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.50.2", + "@rollup/rollup-android-arm64": "4.50.2", + "@rollup/rollup-darwin-arm64": "4.50.2", + "@rollup/rollup-darwin-x64": "4.50.2", + "@rollup/rollup-freebsd-arm64": "4.50.2", + "@rollup/rollup-freebsd-x64": "4.50.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", + "@rollup/rollup-linux-arm-musleabihf": "4.50.2", + "@rollup/rollup-linux-arm64-gnu": "4.50.2", + "@rollup/rollup-linux-arm64-musl": "4.50.2", + "@rollup/rollup-linux-loong64-gnu": "4.50.2", + "@rollup/rollup-linux-ppc64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-musl": "4.50.2", + "@rollup/rollup-linux-s390x-gnu": "4.50.2", + "@rollup/rollup-linux-x64-gnu": "4.50.2", + "@rollup/rollup-linux-x64-musl": "4.50.2", + "@rollup/rollup-openharmony-arm64": "4.50.2", + "@rollup/rollup-win32-arm64-msvc": "4.50.2", + "@rollup/rollup-win32-ia32-msvc": "4.50.2", + "@rollup/rollup-win32-x64-msvc": "4.50.2", "fsevents": "~2.3.2" } }, @@ -4241,11 +4494,19 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -4341,7 +4602,6 @@ "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" }, @@ -4375,12 +4635,39 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "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==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, - "license": "MIT" + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } }, "node_modules/ts-custom-error": { "version": "3.3.1", @@ -4518,9 +4805,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "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": [ { @@ -4536,10 +4823,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -4553,27 +4839,37 @@ "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==", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vite": { - "version": "5.4.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz", + "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", "dev": true, - "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4582,19 +4878,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4615,6 +4917,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -4740,8 +5048,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/yocto-queue": { "version": "0.1.0", diff --git a/frontend/package.json b/frontend/package.json index f587a57..85c00f9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "scs2-0", + "name": "event-approvals", "private": true, "version": "0.0.0", "type": "module", @@ -10,16 +10,19 @@ "preview": "vite preview" }, "dependencies": { + "@react-oauth/google": "^0.12.1", "@zxing/library": "^0.21.3", "axios": "^1.7.7", "bootstrap": "^5.3.3", "chart.js": "^4.4.5", + "jspdf": "^3.0.3", "jwt-decode": "^4.0.0", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0", "react-modal": "^3.16.1", "react-router-dom": "^6.27.0" }, @@ -33,6 +36,6 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.13", "globals": "^15.11.0", - "vite": "^5.4.9" + "vite": "^7.1.6" } } diff --git a/frontend/public/form_header.png b/frontend/public/form_header.png new file mode 100644 index 0000000..4d53d61 Binary files /dev/null and b/frontend/public/form_header.png differ diff --git a/frontend/public/library_image.jpg b/frontend/public/library_image.jpg new file mode 100644 index 0000000..70b7630 Binary files /dev/null and b/frontend/public/library_image.jpg differ diff --git a/frontend/public/staff.png b/frontend/public/staff.png new file mode 100644 index 0000000..30da8a1 Binary files /dev/null and b/frontend/public/staff.png differ diff --git a/frontend/public/student.png b/frontend/public/student.png new file mode 100644 index 0000000..d4c8129 Binary files /dev/null and b/frontend/public/student.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b32765c..e5819f4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,41 +1,50 @@ -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -import Home from './Pages/Home/Home.jsx'; -import Security from './Pages/Security/Security.jsx'; -import ProtectedRoute from './Components/ProtectedRoute/ProtectedRoute.jsx'; -import StudentDashboard from './Pages/Student DashBoard/StudentDashboard.jsx'; -import WardenDashboard from './Pages/WardenDashboard/WardenDashboard.jsx'; +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import Home from "./Pages/Home/Home.jsx"; +import ProtectedRoute from "./Components/ProtectedRoute/ProtectedRoute.jsx"; +import StudentDashboard from "./Pages/Student DashBoard/StudentDashboard.jsx"; +import StaffDashboard from "./Pages/Staff Dashboard/StaffDashboard.jsx"; +import EventDetails from "./Pages/Event Details/EventDetails.jsx"; // Import EventDetails component +import { Toaster } from "react-hot-toast"; // Import the Toaster component +import PageNotFound from "./Pages/Page Not Found/PageNotFound.jsx"; +import { GoogleOAuthProvider } from "@react-oauth/google"; // Import GoogleOAuthProvider function App() { + const GOOGLE_CLIENT_ID ="126283465709-v64j607pjhd396kjsrn7qprhk2dns9ou.apps.googleusercontent.com"; + return ( - - - } /> - - - - } - /> - - - - } - /> - - - - } - /> - - + + + + } /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + {/* Render the Toaster */} + + ); } diff --git a/frontend/src/Components/ApplyLeave/ApplyLeave.css b/frontend/src/Components/ApplyLeave/ApplyLeave.css deleted file mode 100644 index e24e894..0000000 --- a/frontend/src/Components/ApplyLeave/ApplyLeave.css +++ /dev/null @@ -1,57 +0,0 @@ -.leave-container { - display: flex; - justify-content: center; /* Center horizontally */ - align-items: center; /* Center vertically */ - height: 100%; /* Take full height of the parent */ - padding: 20px; /* Add some padding */ - background-color: #f9f9f9; /* Light background color */ - } - - .leave-form { - background-color: white; /* White background for the form */ - padding: 30px; /* Inner padding */ - border-radius: 15px; /* Rounded corners */ - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* Soft shadow */ - width: 100%; /* Full width */ - max-width: 400px; /* Max width for form */ - transition: transform 0.3s ease; /* Smooth transition on hover */ - } - - .form-title { - font-size: 24px; /* Title font size */ - margin-bottom: 20px; /* Space below title */ - text-align: center; /* Center the title text */ - } - - .form-input { - width: 100%; /* Full width */ - padding: 10px; /* Inner padding */ - margin: 10px 0; /* Space between inputs */ - border: 1px solid #ddd; /* Light border */ - border-radius: 5px; /* Rounded corners */ - font-size: 16px; /* Font size */ - transition: border-color 0.3s; /* Smooth border color change */ - } - - .form-input:focus { - border-color: #007bff; /* Change border color on focus */ - outline: none; /* Remove default outline */ - } - - .form-button { - width: 100%; /* Full width */ - padding: 10px; /* Inner padding */ - margin-top: 20px; /* Space above button */ - border: none; /* Remove border */ - border-radius: 5px; /* Rounded corners */ - background-color: #007bff; /* Blue background */ - color: white; /* White text */ - font-size: 16px; /* Font size */ - cursor: pointer; /* Pointer on hover */ - transition: background-color 0.3s; /* Smooth background color change */ - } - - .form-button:hover { - background-color: #0056b3; /* Darker blue on hover */ - } - \ No newline at end of file diff --git a/frontend/src/Components/ApplyLeave/ApplyLeave.jsx b/frontend/src/Components/ApplyLeave/ApplyLeave.jsx deleted file mode 100644 index 0b1d31b..0000000 --- a/frontend/src/Components/ApplyLeave/ApplyLeave.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useState } from 'react'; -import axios from 'axios'; -import './ApplyLeave.css'; -import toast, { Toaster } from "react-hot-toast"; - -function ApplyLeave() { - const [formData, setFormData] = useState({ - placeOfVisit: '', - reason: '', - dateOfLeaving: '', - arrivalDate: '', - emergencyContact: '' - }); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); - }; - - // Get today’s date in YYYY-MM-DD format for validation - const today = new Date().toISOString().split('T')[0]; - - const handleDateValidation = () => { - const { dateOfLeaving, arrivalDate } = formData; - - // Validate that dateOfLeaving is not in the past - if (dateOfLeaving && new Date(dateOfLeaving) < new Date(today)) { - toast.error("Date of leaving cannot be in the past."); - setFormData({ ...formData, dateOfLeaving: '' }); - return; - } - - // Validate that arrivalDate is not before dateOfLeaving - if (arrivalDate && dateOfLeaving && new Date(arrivalDate) < new Date(dateOfLeaving)) { - toast.error("Arrival date cannot be before the date of leaving."); - setFormData({ ...formData, arrivalDate: '' }); - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - const token = localStorage.getItem('token'); - const userID = localStorage.getItem('userID'); - try { - await axios.post('http://localhost:4001/leave/apply', { ...formData, userID }, { - headers: { Authorization: `Bearer ${token}` }, - }); - toast.success('Leave application submitted!'); - setFormData({ - placeOfVisit: '', - reason: '', - dateOfLeaving: '', - arrivalDate: '', - emergencyContact: '' - }); - } catch (error) { - console.error(error); - toast.error(error.response?.data?.message || 'Error applying for leave.'); - } - }; - - return ( -
- -
-

Apply for Leave

- - - - {/* Label and Date Input for Date of Leaving with Minimum Date */} - - - - {/* Label and Date Input for Arrival Date with Validation */} - - - - - -
-
- ); -} - -export default ApplyLeave; diff --git a/frontend/src/Components/ApplyOuting/ApplyOuting.css b/frontend/src/Components/ApplyOuting/ApplyOuting.css deleted file mode 100644 index 4d8bcc2..0000000 --- a/frontend/src/Components/ApplyOuting/ApplyOuting.css +++ /dev/null @@ -1,57 +0,0 @@ -.outing-container { - display: flex; - justify-content: center; /* Center horizontally */ - align-items: center; /* Center vertically */ - height: 100%; /* Take full height of the parent */ - padding: 20px; /* Add some padding */ - background-color: #f9f9f9; /* Light background color */ - } - - .outing-form { - background-color: white; /* White background for the form */ - padding: 30px; /* Inner padding */ - border-radius: 15px; /* Rounded corners */ - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* Soft shadow */ - width: 100%; /* Full width */ - max-width: 400px; /* Max width for form */ - transition: transform 0.3s ease; /* Smooth transition on hover */ - } - - .form-title { - font-size: 24px; /* Title font size */ - margin-bottom: 20px; /* Space below title */ - text-align: center; /* Center the title text */ - } - - .form-input { - width: 100%; /* Full width */ - padding: 10px; /* Inner padding */ - margin: 10px 0; /* Space between inputs */ - border: 1px solid #ddd; /* Light border */ - border-radius: 5px; /* Rounded corners */ - font-size: 16px; /* Font size */ - transition: border-color 0.3s; /* Smooth border color change */ - } - - .form-input:focus { - border-color: #007bff; /* Change border color on focus */ - outline: none; /* Remove default outline */ - } - - .form-button { - width: 100%; /* Full width */ - padding: 10px; /* Inner padding */ - margin-top: 20px; /* Space above button */ - border: none; /* Remove border */ - border-radius: 5px; /* Rounded corners */ - background-color: #007bff; /* Blue background */ - color: white; /* White text */ - font-size: 16px; /* Font size */ - cursor: pointer; /* Pointer on hover */ - transition: background-color 0.3s; /* Smooth background color change */ - } - - .form-button:hover { - background-color: #0056b3; /* Darker blue on hover */ - } - \ No newline at end of file diff --git a/frontend/src/Components/ApplyOuting/ApplyOuting.jsx b/frontend/src/Components/ApplyOuting/ApplyOuting.jsx deleted file mode 100644 index 2f50038..0000000 --- a/frontend/src/Components/ApplyOuting/ApplyOuting.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState } from 'react'; -import axios from 'axios'; -import './ApplyOuting.css'; // Import the CSS for styling -import toast, { Toaster } from "react-hot-toast"; - -function ApplyOuting() { - const [outingData, setOutingData] = useState({ - placeOfVisit: '', - reason: '', - emergencyContact: '' - }); - - const handleChange = (e) => { - const { name, value } = e.target; - setOutingData({ ...outingData, [name]: value }); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - const token = localStorage.getItem('token'); - const userID = localStorage.getItem('userID'); // Get userID from local storage - try { - await axios.post('http://localhost:4001/out/outapply', { ...outingData, userID }, { // Include userID in the request body - headers: { Authorization: `Bearer ${token}` }, - }); - toast.success('Outing application submitted!'); // Use toast for success notification - // Reset form data after submission - setOutingData({ - placeOfVisit: '', - reason: '', - emergencyContact: '' - }); - } catch (error) { - console.error(error); - toast.error("Error: " + error.response.data.message); - } - }; - - return ( -
- {/* Add the Toaster component here */} -
-

Apply for Outing

- - - - -
-
- ); -} - -export default ApplyOuting; diff --git a/frontend/src/Components/BarCodeScanner/BarCodeScanner.jsx b/frontend/src/Components/BarCodeScanner/BarCodeScanner.jsx deleted file mode 100644 index b0a4eec..0000000 --- a/frontend/src/Components/BarCodeScanner/BarCodeScanner.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { BrowserMultiFormatReader, BarcodeFormat } from "@zxing/library"; - -const BarcodeScanner = ({ onScan, onScanAgain }) => { - const [result, setResult] = useState(null); - const [videoDevice, setVideoDevice] = useState(null); - const [scanning, setScanning] = useState(true); // Track scanning state - - useEffect(() => { - const reader = new BrowserMultiFormatReader(); - - async function init() { - try { - const videoDevices = await reader.listVideoInputDevices(); - - if (videoDevices.length > 0) { - setVideoDevice(videoDevices[0]); // Use the first available video device - } - } catch (error) { - console.error("Error listing video devices:", error); - } - } - - init(); - - // Cleanup function for reader - return () => { - reader.reset(); // Ensure that the reader is reset when the component unmounts - }; - }, []); - - useEffect(() => { - const videoElement = document.getElementById('video'); - const reader = new BrowserMultiFormatReader(); - - if (videoDevice && scanning) { - reader.decodeFromVideoDevice(videoDevice.deviceId, videoElement, (result) => { - if (result) { - // Check if the result format is a barcode format - if ( - result.format === BarcodeFormat.CODE_39 || - result.format === BarcodeFormat.CODE_128 || - result.format === BarcodeFormat.EAN_13 || - result.format === BarcodeFormat.EAN_8 || - result.format === BarcodeFormat.UPC_A || - result.format === BarcodeFormat.UPC_E || - result.format === BarcodeFormat.ITF - ) { - setResult(result.text); - onScan(result.text); // Send the scanned result to the parent component - setScanning(false); // Stop scanning after a successful scan - reader.reset(); // Stop the reader - } - } - }, { - // Set the formats to only decode the desired barcode formats - formats: [ - BarcodeFormat.CODE_39, - BarcodeFormat.CODE_128, - BarcodeFormat.EAN_13, - BarcodeFormat.EAN_8, - BarcodeFormat.UPC_A, - BarcodeFormat.UPC_E, - BarcodeFormat.ITF, - ], - }); - - // Cleanup function to stop the video stream when the videoDevice changes or scanning stops - return () => { - reader.reset(); // Stop the reader when cleaning up - }; - } - }, [videoDevice, scanning]); - - const handleScanAgain = () => { - setResult(null); // Reset the result - setScanning(true); // Start scanning again - onScanAgain(); // Call the scan again function from props - }; - - return ( -
- {result ? ( -
-

Scanned Code: {result}

- {/* Button to scan again */} -
- ) : ( -

Scanning...

- )} -
- ); -}; - -export default BarcodeScanner; diff --git a/frontend/src/Components/EventForm/EventForm.css b/frontend/src/Components/EventForm/EventForm.css new file mode 100644 index 0000000..b76c258 --- /dev/null +++ b/frontend/src/Components/EventForm/EventForm.css @@ -0,0 +1,90 @@ +/* Container styling for the form */ +.event-form-container { + max-width: 850px; + background-color: #ffffff; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.event-form-container h2 { + font-weight: 600; + color: #343a40; +} + +.event-form-container h4 { + color: #007bff; + border-bottom: 2px solid #007bff; + padding-bottom: 0.5rem; + margin-top: 2rem; +} + +.form-label { + font-weight: 500; + color: #495057; +} + +.form-control:focus, +.form-select:focus { + border-color: #80bdff; + box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25); +} + +.btn { + padding: 0.5rem 1.5rem; + font-size: 1rem; + font-weight: 500; + border-radius: 0.25rem; + transition: all 0.2s ease-in-out; +} + +.btn-primary { + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:hover { + background-color: #0056b3; + border-color: #0056b3; + transform: translateY(-2px); +} + +.btn-success { + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:hover { + background-color: #1e7e34; + border-color: #1e7e34; + transform: translateY(-2px); + } + +.btn:disabled { + cursor: not-allowed; + opacity: 0.65; +} + +.form-check-label { + line-height: 1.5; + color: #333; +} + +.form-check-input { + margin-top: 0.4em; +} + +@media (max-width: 576px) { + .event-form-container { + padding: 1.5rem; + } + + .btn { + width: 100%; + margin-bottom: 0.5rem; + } + + .d-flex.justify-content-start { + flex-direction: column; + } +} \ No newline at end of file diff --git a/frontend/src/Components/EventForm/EventForm.jsx b/frontend/src/Components/EventForm/EventForm.jsx new file mode 100644 index 0000000..4c54c36 --- /dev/null +++ b/frontend/src/Components/EventForm/EventForm.jsx @@ -0,0 +1,568 @@ +import { useState } from "react"; +import { jsPDF } from "jspdf"; // Assuming this is used within generatePDF +import "bootstrap/dist/css/bootstrap.min.css"; +import toast, { Toaster } from "react-hot-toast"; +import API from '/src/api/api'; +import { generatePDF } from "../../utils/pdfGenerator"; +import StudentDashboard from "../StudentDashboard/EventDashboard.jsx"; +import "./EventForm.css"; + +const EventForm = () => { + const [formData, setFormData] = useState( + + //Event Details: + eventName: "", //1a (index no as in official form) + partOfGymkhanaCalendar: "",//1b + eventType: "", //extra variable: Not in official form, just to track type of event + clubName: "", //2 + startDate: "", //3a + endDate: "", //3b + eventVenue: "", //3c + sourceOfBudget: "", //4a + othersSourceOfBudget: "", //4b + estimatedBudget: 0, //5 - Set initial value to 0 + budgetBreakup: [{ expenseHead: "", estimatedAmount: "" }], //array to hold the data of the "Budget Breakup" Table's rows + + //Organizer Details: + nameOfTheOrganizer: "", //1 + designation: "", //2 + email: "", //3 + phoneNumber: "", //4 + + //Requirements + requirements: [], + anyAdditionalAmenities: "", + // Event Description + eventDescription: "", + // Participants + internalParticipants: "", + externalParticipants: "", + listOfCollaboratingOrganizations: "" + }); + + const [isAgreementChecked, setisAgreementChecked] = useState(false); + const [isFormSubmitted, setIsFormSubmitted] = useState(false); + + // Get today's date in YYYY-MM-DD format for min attribute in date inputs + const today = new Date().toISOString().split("T")[0]; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleCheckboxChange = (e) => { + const { value, checked } = e.target; + setFormData((prevData) => ({ + ...prevData, + requirements: checked + ? [...prevData.requirements, value] + : prevData.requirements.filter((item) => item !== value), + })); + }; + + const handleDateValidation = () => { + // Validate that endDate is not before startDate + if (formData.startDate && formData.endDate && new Date(formData.endDate) < new Date(formData.startDate)) { + toast.error("End Date cannot be before the Start Date."); + setFormData({ ...formData, endDate: "" }); + } + }; + + const validateForm = () => { + const requiredFields = [ + "eventName", "partOfGymkhanaCalendar", "eventType", "clubName", "startDate", "endDate", + "eventVenue", "sourceOfBudget", "estimatedBudget", "nameOfTheOrganizer", "designation", + "email", "phoneNumber", "eventDescription", "externalParticipants", "internalParticipants" + ]; + + if (requiredFields.some(field => !formData[field])) { + return false; + } + + // Conditionally require listOfCollaboratingOrganizations + if (Number(formData.externalParticipants) > 0 && !formData.listOfCollaboratingOrganizations) { + return false; + } + + return true; + }; + + // Handlers for the Budget Breakup Table + const handleBudgetChange = (index, e) => { + const { name, value } = e.target; + const list = [...formData.budgetBreakup]; + list[index][name] = value; + + // Recalculate the total budget + const total = list.reduce((acc, curr) => acc + Number(curr.estimatedAmount || 0), 0); + + setFormData({ ...formData, budgetBreakup: list, estimatedBudget: total }); + }; + + const handleAddRow = () => { + setFormData({ + ...formData, + budgetBreakup: [...formData.budgetBreakup, { expenseHead: "", estimatedAmount: "" }], + }); + }; + + const handleRemoveRow = (index) => { + const list = [...formData.budgetBreakup]; + list.splice(index, 1); + + // Recalculate the total budget + const total = list.reduce((acc, curr) => acc + Number(curr.estimatedAmount || 0), 0); + + setFormData({ ...formData, budgetBreakup: list, estimatedBudget: total }); + }; + + const handleSubmit = async () => { + if (!validateForm()) { + toast.error("Please fill out all required fields."); + return; + } + + const userID = localStorage.getItem("userID"); + const requestData = { ...formData, userID }; + + try { + const response = await API.post("/event/apply", requestData); + toast.success(response.data.message || "Event proposal submitted successfully!"); + setTimeout(() => { + setIsFormSubmitted(true); + }, 1200); + } catch (error) { + console.error("Error submitting event:", error); + toast.error(error.response?.data?.message || "Failed to submit the proposal."); + } + }; + + if (isFormSubmitted) { + return ; + } + + return ( +
+ +

Event Proposal Form

+
+ {/* Event Details Section */} +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + {formData.sourceOfBudget === "Others" ? ( +
+ + +
+ ) : ( +
+ {/*Hidden*/} +
+ ) + } +
+ + +
+ + {/* Budget Breakup Table */} +
+ + + + + + + + + + + {formData.budgetBreakup.map((item, index) => ( + + + + + { + + } + + + + ))} + +
Sl.NoExpense HeadEstimated Amount (₹)
{index + 1} + handleBudgetChange(index, e)} + placeholder="e.g., Refreshments" + required + /> + + handleBudgetChange(index, e)} + placeholder="0" + min="0" + required + /> + + {formData.budgetBreakup.length > 0 && ( + + )} +
+ +
+ + {/* Organizer Details */} +
+ + +
+ + + {/* Organizer Details Section */} +

Organizer Details

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + {/* Requirements Section */} +
+ + {["Security", "Transport", "IPS Related", "Housekeeping", "Refreshment", "Ambulance", "Networking"].map( + (req) => ( +
+ + +
+ ) + )} +
+ +
+ + +
+ + + {/* Event Description Section */} +
+ + +
+ + {/* Participants Section */} +

Expected Number of Participants

+
+
+ + +
+
+ + +
+
+ + {Number(formData.externalParticipants) > 0 && ( +
+ + +
+ )} + + {/* Agreement and Submission */} +
+ setisAgreementChecked(!isAgreementChecked)} + /> + +
+ +
+ + +
+
+ + +
+ ); +}; + +export default EventForm; \ No newline at end of file diff --git a/frontend/src/Components/Footer/Footer.css b/frontend/src/Components/Footer/Footer.css index 1dfb166..63560b0 100644 --- a/frontend/src/Components/Footer/Footer.css +++ b/frontend/src/Components/Footer/Footer.css @@ -1,8 +1,39 @@ -footer{ - background-color: #291334; +footer { + /* background-color: #000000 !important; Ensure black background */ color: #FFFFFF; + width: 100%; } + +.full-width-footer { + width: 100%; + margin: 0; /* Remove default margins */ + padding: 0; /* Adjust padding if needed */ + background-color: #291334; /* Ensure consistent background color */ +} + +.full-width-footer .container { + max-width: 100%; /* Override Bootstrap's default max-width */ + padding: 0; /* Remove container padding for full-width effect */ +} + +.full-width-footer { + overflow-x: hidden; +} + + +footer.container { + max-width: 100%; + padding: 0; + border: 2px solid #F2911B; /* Optional: Add a border for separation */ + +} +.footer-main { + background-color: #291334; /* Change to black */ + color: #FFFFFF; /* Text color */ + padding: 20px; /* Optional: Add padding */ +} + .foot-link{ font-weight: 500; position: relative; @@ -45,3 +76,8 @@ hr{ p{ margin-bottom: 0; } + +div.copyright { + background-color: #291334; /* Change to black */ + padding: 10px; +} \ No newline at end of file diff --git a/frontend/src/Components/Footer/Footer.jsx b/frontend/src/Components/Footer/Footer.jsx index e326771..c2b864b 100644 --- a/frontend/src/Components/Footer/Footer.jsx +++ b/frontend/src/Components/Footer/Footer.jsx @@ -4,9 +4,8 @@ import "./Footer.css" const Footer = () => { return ( -