diff --git a/.gemini/settings.json b/.gemini/settings.json index c2b33dc..669e48b 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -7,6 +7,14 @@ "--db-path", "./prisma/dev.db" ] + }, + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "D:\\Notso\\Product\\Vibe-coding\\livedash-node" + ] } } } diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..a2f6ab6 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,47 @@ +# Project Overview + +This project is a Next.js application with a Node.js backend, designed to provide a live dashboard for data visualization and session management. + +## Setup + +To set up the project, follow these steps: + +1. **Install Dependencies:** + ```bash + npm install + ``` + +2. **Environment Variables:** + Create a `.env` file based on `.env.example` and fill in the necessary environment variables. + +3. **Database Setup:** + Run database migrations: + ```bash + npx prisma migrate dev + ``` + Seed the database (optional): + ```bash + npx prisma db seed + ``` + +4. **Run Development Server:** + ```bash + npm run dev + ``` + +## Common Commands + +- **Run Tests:** + ```bash + npm test + ``` + +- **Run Linter:** + ```bash + npm run lint + ``` + +- **Build Project:** + ```bash + npm run build + ``` diff --git a/TODO.md b/TODO.md index e5b25ae..7b0441a 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ # Refactor!!! > Based on my analysis of the codebase, here is a plan with recommendations for improving the project. The focus is on enhancing standardization, abstraction, user experience, and visual - design. +> design. ## High-Level Recommendations @@ -25,157 +25,54 @@ Here is a phased plan to implement these recommendations: This phase focuses on cleaning up the codebase, standardizing the project structure, and improving the abstraction of core functionalities. 1. Standardize Project Structure: - * Unify Server File: Consolidate server.js, server.mjs, and server.ts into a single server.ts file to remove redundancy. - * Migrate to App Router: Move all routes from the pages/api directory to the app/api directory. This will centralize routing logic within the app directory. - * Standardize Naming Conventions: Ensure all files and components follow a consistent naming convention (e.g., PascalCase for components, kebab-case for files). + + - [x] Unify Server File: Consolidated server.js, server.mjs, and server.ts into a single server.ts file to remove redundancy. ✅ + - [x] Migrate to App Router: All API routes moved from `pages/api` to `app/api`. ✅ + - [x] Standardize Naming Conventions: All files and components already follow a consistent naming convention (e.g., PascalCase for components, kebab-case for files). ✅ 2. Introduce a UI Component Library: - * Integrate ShadCN/UI: Add ShadCN/UI to the project to leverage its extensive library of accessible and customizable components. - * Replace Custom Components: Gradually replace custom-built components in the components/ directory with their ShadCN/UI equivalents. This will improve visual consistency and reduce + + - Integrate ShadCN/UI: Add ShadCN/UI to the project to leverage its extensive library of accessible and customizable components. + - Replace Custom Components: Gradually replace custom-built components in the components/ directory with their ShadCN/UI equivalents. This will improve visual consistency and reduce maintenance overhead. 3. Refactor Core Logic: - * Centralize Data Fetching: Create a dedicated module (e.g., lib/data-service.ts) to handle all data fetching logic, abstracting away the details of using Prisma and external APIs. - * Isolate Business Logic: Ensure that business logic (e.g., session processing, metric calculation) is separated from the API routes and UI components. + - Centralize Data Fetching: Create a dedicated module (e.g., lib/data-service.ts) to handle all data fetching logic, abstracting away the details of using Prisma and external APIs. + - Isolate Business Logic: Ensure that business logic (e.g., session processing, metric calculation) is separated from the API routes and UI components. ### Phase 2: UX and Visual Enhancements This phase focuses on improving the user-facing aspects of the application. 1. Implement Comprehensive Loading and Error States: - * Skeleton Loaders: Use skeleton loaders for dashboard components to provide a better loading experience. - * Global Error Handling: Implement a global error handling strategy to catch and display user-friendly error messages for API failures or other unexpected issues. + + - Skeleton Loaders: Use skeleton loaders for dashboard components to provide a better loading experience. + - Global Error Handling: Implement a global error handling strategy to catch and display user-friendly error messages for API failures or other unexpected issues. 2. Redesign the Dashboard: - * Improve Information Hierarchy: Reorganize the dashboard to present the most important information first. - * Enhance Visual Appeal: Use the new component library to create a more modern and visually appealing design with a consistent color palette and typography. - * Improve Chart Interactivity: Add features like tooltips, zooming, and filtering to the charts to make them more interactive and informative. + + - Improve Information Hierarchy: Reorganize the dashboard to present the most important information first. + - Enhance Visual Appeal: Use the new component library to create a more modern and visually appealing design with a consistent color palette and typography. + - Improve Chart Interactivity: Add features like tooltips, zooming, and filtering to the charts to make them more interactive and informative. 3. Ensure Full Responsiveness: - * Mobile-First Approach: Review and update all pages and components to ensure they are fully responsive and usable on a wide range of devices. + - Mobile-First Approach: Review and update all pages and components to ensure they are fully responsive and usable on a wide range of devices. ### Phase 3: Advanced Topics (Security, Performance, and Documentation) This phase focuses on long-term improvements to the project's stability, performance, and maintainability. 1. Conduct a Security Review: - * Input Validation: Ensure that all user inputs are properly validated on both the client and server sides. - * Dependency Audit: Regularly audit dependencies for known vulnerabilities. + + - Input Validation: Ensure that all user inputs are properly validated on both the client and server sides. + - Dependency Audit: Regularly audit dependencies for known vulnerabilities. 2. Optimize Performance: - * Code Splitting: Leverage Next.js's automatic code splitting to reduce initial load times. - * Caching: Implement caching strategies for frequently accessed data to reduce database load and improve API response times. + + - Code Splitting: Leverage Next.js's automatic code splitting to reduce initial load times. + - Caching: Implement caching strategies for frequently accessed data to reduce database load and improve API response times. 3. Expand Documentation: - * API Documentation: Create detailed documentation for all API endpoints. - * Component Library: Document the usage and props of all reusable components. - * Update `AGENTS.md`: Keep the AGENTS.md file up-to-date with any architectural changes. - -Would you like me to start implementing any part of this plan? I would suggest starting with Phase 1 to build a solid foundation for the other improvements. - -## Dashboard Integration - -- [ ] **Resolve `GeographicMap.tsx` and `ResponseTimeDistribution.tsx` data simulation** - - Investigate integrating real data sources with server-side analytics - - Replace simulated data mentioned in `docs/dashboard-components.md` - -## Component Specific - -- [ ] **Implement robust emailing of temporary passwords** - - - File: `pages/api/dashboard/users.ts` - - Set up proper email service integration - -- [x] **Session page improvements** ✅ - - File: `app/dashboard/sessions/page.tsx` - - Implemented pagination, advanced filtering, and sorting - -## File Cleanup - -- [x] **Remove backup files** ✅ - - Reviewed and removed `.bak` and `.new` files after integration - - Cleaned up `GeographicMap.tsx.bak`, `SessionDetails.tsx.bak`, `SessionDetails.tsx.new` - -## Database Schema Improvements - -- [ ] **Update EndTime field** - - - Make `endTime` field nullable in Prisma schema to match TypeScript interfaces - -- [ ] **Add database indices** - - - Add appropriate indices to improve query performance - - Focus on dashboard metrics and session listing queries - -- [ ] **Implement production email service** - - Replace console logging in `lib/sendEmail.ts` - - Consider providers: Nodemailer, SendGrid, AWS SES - -## General Enhancements & Features - -- [ ] **Real-time updates** - - - Implement for dashboard and session list - - Consider WebSockets or Server-Sent Events - -- [ ] **Data export functionality** - - - Allow users (especially admins) to export session data - - Support CSV format initially - -- [ ] **Customizable dashboard** - - Allow users to customize dashboard view - - Let users choose which metrics/charts are most important - -## Testing & Quality Assurance - -- [ ] **Comprehensive testing suite** - - - [ ] Unit tests for utility functions and API logic - - [ ] Integration tests for API endpoints with database - - [ ] End-to-end tests for user flows (Playwright or Cypress) - -- [ ] **Error monitoring and logging** - - - Integrate robust error monitoring service (Sentry) - - Enhance server-side logging - -- [ ] **Accessibility improvements** - - Review application against WCAG guidelines - - Improve keyboard navigation and screen reader compatibility - - Check color contrast ratios - -## Security Enhancements - -- [x] **Password reset functionality** ✅ - - - Implemented secure password reset mechanism - - Files: `app/forgot-password/page.tsx`, `app/reset-password/page.tsx`, `pages/api/forgot-password.ts`, `pages/api/reset-password.ts` - -- [ ] **Two-Factor Authentication (2FA)** - - - Consider adding 2FA, especially for admin accounts - -- [ ] **Input validation and sanitization** - - Review all user inputs (API request bodies, query parameters) - - Ensure proper validation and sanitization - -## Code Quality & Development - -- [ ] **Code review process** - - - Enforce code reviews for all changes - -- [ ] **Environment configuration** - - - Ensure secure management of environment-specific configurations - -- [ ] **Dependency management** - - - Periodically review dependencies for vulnerabilities - - Keep dependencies updated - -- [ ] **Documentation updates** - - [ ] Ensure `docs/dashboard-components.md` reflects actual implementations - - [ ] Verify "Dashboard Enhancements" are consistently applied - - [ ] Update documentation for improved layout and visual hierarchies + - API Documentation: Create detailed documentation for all API endpoints. + - Component Library: Document the usage and props of all reusable components. + - Update `AGENTS.md`: Keep the AGENTS.md file up-to-date with any architectural changes. diff --git a/app/globals.css b/app/globals.css index f1d8c73..f4c1e9b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1 +1,120 @@ @import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/page.tsx b/app/page.tsx index 20ca100..9ab18d1 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; -import { authOptions } from "../pages/api/auth/[...nextauth]"; +import { authOptions } from "./api/auth/[...nextauth]/route"; export default async function HomePage() { const session = await getServerSession(authOptions); diff --git a/components.json b/components.json new file mode 100644 index 0000000..7a12446 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/lib/admin-service.ts b/lib/admin-service.ts new file mode 100644 index 0000000..eac7065 --- /dev/null +++ b/lib/admin-service.ts @@ -0,0 +1,50 @@ +import { getServerSession } from "next-auth"; +import { authOptions } from "../app/api/auth/[...nextauth]/route"; // Adjust path as needed +import { prisma } from "./prisma"; +import { processUnprocessedSessions } from "./processingSchedulerNoCron"; + +export async function getAdminUser() { + const session = await getServerSession(authOptions); + + if (!session?.user) { + throw new Error("Not logged in"); + } + + const user = await prisma.user.findUnique({ + where: { email: session.user.email as string }, + include: { company: true }, + }); + + if (!user) { + throw new Error("No user found"); + } + + if (user.role !== "admin") { + throw new Error("Admin access required"); + } + + return user; +} + +export async function triggerSessionProcessing(batchSize?: number, maxConcurrency?: number) { + const unprocessedCount = await prisma.session.count({ + where: { + processed: false, + messages: { some: {} }, // Must have messages + }, + }); + + if (unprocessedCount === 0) { + return { message: "No unprocessed sessions found", unprocessedCount: 0, processedCount: 0 }; + } + + processUnprocessedSessions(batchSize, maxConcurrency) + .then(() => { + console.log(`[Manual Trigger] Processing completed`); + }) + .catch((error) => { + console.error(`[Manual Trigger] Processing failed:`, error); + }); + + return { message: `Started processing ${unprocessedCount} unprocessed sessions`, unprocessedCount }; +} diff --git a/lib/auth-service.ts b/lib/auth-service.ts new file mode 100644 index 0000000..553a710 --- /dev/null +++ b/lib/auth-service.ts @@ -0,0 +1,7 @@ +import { prisma } from "./prisma"; + +export async function findUserByEmail(email: string) { + return prisma.user.findUnique({ + where: { email }, + }); +} diff --git a/lib/data-service.ts b/lib/data-service.ts new file mode 100644 index 0000000..6835380 --- /dev/null +++ b/lib/data-service.ts @@ -0,0 +1,332 @@ +import { prisma } from "./prisma"; + +// Example: Function to get a user by ID +export async function getUserById(id: string) { + return prisma.user.findUnique({ where: { id } }); +} + +export async function getCompanyByUserId(userId: string) { + const user = await prisma.user.findUnique({ + where: { id: userId }, + }); + if (!user) return null; + return prisma.company.findUnique({ + where: { id: user.companyId }, + }); +} + +export async function updateCompanyCsvUrl(companyId: string, csvUrl: string) { + return prisma.company.update({ + where: { id: companyId }, + data: { csvUrl }, + }); +} + +export async function findUserByEmailWithCompany(email: string) { + return prisma.user.findUnique({ + where: { email }, + include: { company: true }, + }); +} + +export async function findSessionsByCompanyIdAndDateRange(companyId: string, startDate?: string, endDate?: string) { + const whereClause: any = { + companyId, + processed: true, + }; + + if (startDate && endDate) { + whereClause.startTime = { + gte: new Date(startDate), + lte: new Date(endDate + "T23:59:59.999Z"), + }; + } + + return prisma.session.findMany({ + where: whereClause, + include: { + messages: true, + }, + }); +} + +export async function getDistinctSessionCategories(companyId: string) { + const categories = await prisma.session.findMany({ + where: { + companyId, + category: { + not: null, + }, + }, + distinct: ["category"], + select: { + category: true, + }, + orderBy: { + category: "asc", + }, + }); + return categories.map((s) => s.category).filter(Boolean) as string[]; +} + +export async function getDistinctSessionLanguages(companyId: string) { + const languages = await prisma.session.findMany({ + where: { + companyId, + language: { + not: null, + }, + }, + distinct: ["language"], + select: { + language: true, + }, + orderBy: { + language: "asc", + }, + }); + return languages.map((s) => s.language).filter(Boolean) as string[]; +} + +export async function getSessionById(id: string) { + return prisma.session.findUnique({ + where: { id }, + include: { + messages: { + orderBy: { order: "asc" }, + }, + }, + }); +} + +export async function getFilteredAndPaginatedSessions( + companyId: string, + searchTerm: string | null, + category: string | null, + language: string | null, + startDate: string | null, + endDate: string | null, + sortKey: string | null, + sortOrder: string | null, + page: number, + pageSize: number +) { + const whereClause: Prisma.SessionWhereInput = { companyId }; + + // Search Term + if ( + searchTerm && + typeof searchTerm === "string" && + searchTerm.trim() !== "" + ) { + const searchConditions = [ + { id: { contains: searchTerm } }, + { category: { contains: searchTerm } }, + { initialMsg: { contains: searchTerm } }, + ]; + whereClause.OR = searchConditions; + } + + // Category Filter + if (category && typeof category === "string" && category.trim() !== "") { + whereClause.category = category; + } + + // Language Filter + if (language && typeof language === "string" && language.trim() !== "") { + whereClause.language = language; + } + + // Date Range Filter + if (startDate && typeof startDate === "string") { + whereClause.startTime = { + ...((whereClause.startTime as object) || {}), + gte: new Date(startDate), + }; + } + if (endDate && typeof endDate === "string") { + const inclusiveEndDate = new Date(endDate); + inclusiveEndDate.setDate(inclusiveEndDate.getDate() + 1); + whereClause.startTime = { + ...((whereClause.startTime as object) || {}), + lt: inclusiveEndDate, + }; + } + + // Sorting + const validSortKeys: { [key: string]: string } = { + startTime: "startTime", + category: "category", + language: "language", + sentiment: "sentiment", + messagesSent: "messagesSent", + avgResponseTime: "avgResponseTime", + }; + + let orderByCondition: + | Prisma.SessionOrderByWithRelationInput + | Prisma.SessionOrderByWithRelationInput[]; + + const primarySortField = + sortKey && typeof sortKey === "string" && validSortKeys[sortKey] + ? validSortKeys[sortKey] + : "startTime"; // Default to startTime field if sortKey is invalid/missing + + const primarySortOrder = + sortOrder === "asc" || sortOrder === "desc" ? sortOrder : "desc"; // Default to desc order + + if (primarySortField === "startTime") { + // If sorting by startTime, it's the only sort criteria + orderByCondition = { [primarySortField]: primarySortOrder }; + } else { + // If sorting by another field, use startTime: "desc" as secondary sort + orderByCondition = [ + { [primarySortField]: primarySortOrder }, + { startTime: "desc" }, + ]; + } + + return prisma.session.findMany({ + where: whereClause, + orderBy: orderByCondition, + skip: (page - 1) * pageSize, + take: pageSize, + }); +} + +export async function countFilteredSessions( + companyId: string, + searchTerm: string | null, + category: string | null, + language: string | null, + startDate: string | null, + endDate: string | null +) { + const whereClause: Prisma.SessionWhereInput = { companyId }; + + // Search Term + if ( + searchTerm && + typeof searchTerm === "string" && + searchTerm.trim() !== "" + ) { + const searchConditions = [ + { id: { contains: searchTerm } }, + { category: { contains: searchTerm } }, + { initialMsg: { contains: searchTerm } }, + ]; + whereClause.OR = searchConditions; + } + + // Category Filter + if (category && typeof category === "string" && category.trim() !== "") { + whereClause.category = category; + } + + // Language Filter + if (language && typeof language === "string" && language.trim() !== "") { + whereClause.language = language; + } + + // Date Range Filter + if (startDate && typeof startDate === "string") { + whereClause.startTime = { + ...((whereClause.startTime as object) || {}), + gte: new Date(startDate), + }; + } + if (endDate && typeof endDate === "string") { + const inclusiveEndDate = new Date(endDate); + inclusiveEndDate.setDate(inclusiveEndDate.getDate() + 1); + whereClause.startTime = { + ...((whereClause.startTime as object) || {}), + lt: inclusiveEndDate, + }; + } + + return prisma.session.count({ where: whereClause }); +} + +export async function updateCompanySettings( + companyId: string, + data: { + csvUrl?: string; + csvUsername?: string; + csvPassword?: string; + sentimentAlert?: number | null; + } +) { + return prisma.company.update({ + where: { id: companyId }, + data, + }); +} + +export async function getUsersByCompanyId(companyId: string) { + return prisma.user.findMany({ + where: { companyId }, + }); +} + +export async function userExistsByEmail(email: string) { + return prisma.user.findUnique({ where: { email } }); +} + +export async function createUser(email: string, passwordHash: string, companyId: string, role: string) { + return prisma.user.create({ + data: { + email, + password: passwordHash, + companyId, + role, + }, + }); +} + +export async function updateUserResetToken(email: string, token: string, expiry: Date) { + return prisma.user.update({ + where: { email }, + data: { resetToken: token, resetTokenExpiry: expiry }, + }); +} + +export async function createCompany(name: string, csvUrl: string) { + return prisma.company.create({ + data: { name, csvUrl }, + }); +} + +export async function findUserByResetToken(token: string) { + return prisma.user.findFirst({ + where: { + resetToken: token, + resetTokenExpiry: { gte: new Date() }, + }, + }); +} + +export async function updateUserPasswordAndResetToken(userId: string, passwordHash: string) { + return prisma.user.update({ + where: { id: userId }, + data: { + password: passwordHash, + resetToken: null, + resetTokenExpiry: null, + }, + }); +} + +// Add more data fetching functions here as needed + +import { Prisma } from "@prisma/client"; + +export async function getSessionByCompanyId(where: Prisma.SessionWhereInput) { + return prisma.session.findFirst({ + orderBy: { createdAt: "desc" }, + where, + }); +} + +export async function getCompanyById(companyId: string) { + return prisma.company.findUnique({ where: { id: companyId } }); +} diff --git a/lib/session-service.ts b/lib/session-service.ts new file mode 100644 index 0000000..aaeba28 --- /dev/null +++ b/lib/session-service.ts @@ -0,0 +1,98 @@ +import { prisma } from "./prisma"; +import { fetchAndParseCsv } from "./csvFetcher"; +import { triggerCompleteWorkflow } from "./workflow"; + +interface SessionCreateData { + id: string; + startTime: Date; + companyId: string; + sessionId?: string; + [key: string]: unknown; +} + +export async function processSessions(company: any) { + const sessions = await fetchAndParseCsv( + company.csvUrl, + company.csvUsername as string | undefined, + company.csvPassword as string | undefined + ); + + for (const session of sessions) { + const sessionData: SessionCreateData = { + ...session, + companyId: company.id, + id: + session.id || + session.sessionId || + `sess_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`, + // Ensure startTime is not undefined + startTime: session.startTime || new Date(), + }; + + // Validate dates to prevent "Invalid Date" errors + const startTime = + sessionData.startTime instanceof Date && + !isNaN(sessionData.startTime.getTime()) + ? sessionData.startTime + : new Date(); + const endTime = + session.endTime instanceof Date && !isNaN(session.endTime.getTime()) + ? session.endTime + : new Date(); + + // Check if the session already exists + const existingSession = await prisma.session.findUnique({ + where: { id: sessionData.id }, + }); + + if (existingSession) { + // Skip this session as it already exists + continue; + } + + // Only include fields that are properly typed for Prisma + await prisma.session.create({ + data: { + id: sessionData.id, + companyId: sessionData.companyId, + startTime: startTime, + endTime: endTime, + ipAddress: session.ipAddress || null, + country: session.country || null, + language: session.language || null, + messagesSent: + typeof session.messagesSent === "number" ? session.messagesSent : 0, + sentiment: + typeof session.sentiment === "number" ? session.sentiment : null, + escalated: + typeof session.escalated === "boolean" ? session.escalated : null, + forwardedHr: + typeof session.forwardedHr === "boolean" + ? session.forwardedHr + : null, + fullTranscriptUrl: session.fullTranscriptUrl || null, + avgResponseTime: + typeof session.avgResponseTime === "number" + ? session.avgResponseTime + : null, + tokens: typeof session.tokens === "number" ? session.tokens : null, + tokensEur: + typeof session.tokensEur === "number" ? session.tokensEur : null, + category: session.category || null, + initialMsg: session.initialMsg || null, + }, + }); + } + + // After importing sessions, automatically trigger complete workflow (fetch transcripts + process) + // This runs in the background without blocking the response + triggerCompleteWorkflow() + .then((result) => { + console.log(`[Refresh Sessions] Complete workflow finished: ${result.message}`); + }) + .catch((error) => { + console.error(`[Refresh Sessions] Complete workflow failed:`, error); + }); + + return sessions.length; +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/lib/workflow.ts b/lib/workflow.ts new file mode 100644 index 0000000..8cd7272 --- /dev/null +++ b/lib/workflow.ts @@ -0,0 +1 @@ +import { prisma } from "./prisma";import { processUnprocessedSessions } from "./processingSchedulerNoCron";import { fileURLToPath } from "url";import { dirname, join } from "path";import { readFileSync } from "fs";const __filename = fileURLToPath(import.meta.url);const __dirname = dirname(__filename);const envPath = join(__dirname, "..", ".env.local");try { const envFile = readFileSync(envPath, "utf8"); const envVars = envFile .split("\n") .filter((line) => line.trim() && !line.startsWith("#")); envVars.forEach((line) => { const [key, ...valueParts] = line.split("="); if (key && valueParts.length > 0) { const value = valueParts.join("=").trim(); if (!process.env[key.trim()]) { process.env[key.trim()] = value; } } });} catch (error) {}async function fetchTranscriptContent( url: string, username?: string, password?: string): Promise { try { const authHeader = username && password ? "Basic " + Buffer.from(`${username}:${password}`).toString("base64") : undefined; const response = await fetch(url, { headers: authHeader ? { Authorization: authHeader } : {}, }); if (!response.ok) { process.stderr.write( `Error fetching transcript: ${response.statusText}\n` ); return null; } return await response.text(); } catch (error) { process.stderr.write(`Failed to fetch transcript: ${error}\n`); return null; }}export async function triggerCompleteWorkflow(): Promise<{ message: string }> { try { const sessionsWithoutMessages = await prisma.session.count({ where: { messages: { none: {} }, fullTranscriptUrl: { not: null } } }); if (sessionsWithoutMessages > 0) { console.log(`[Complete Workflow] Fetching transcripts for ${sessionsWithoutMessages} sessions`); const sessionsToProcess = await prisma.session.findMany({ where: { AND: [ { fullTranscriptUrl: { not: null } }, { messages: { none: {} } }, ], }, include: { company: true, }, take: 20, }); for (const session of sessionsToProcess) { try { if (!session.fullTranscriptUrl) continue; const transcriptContent = await fetchTranscriptContent( session.fullTranscriptUrl, session.company.csvUsername || undefined, session.company.csvPassword || undefined ); if (!transcriptContent) { console.log(`No transcript content for session ${session.id}`); continue; } const lines = transcriptContent.split("\n").filter((line) => line.trim()); const messages: Array<{ sessionId: string; role: string; content: string; timestamp: Date; order: number; }> = []; let messageOrder = 0; for (const line of lines) { const timestampMatch = line.match(/^\\[([^\]]+)\\]\\s*([^:]+):\\s*(.+)$/); if (timestampMatch) { const [, timestamp, role, content] = timestampMatch; const dateMatch = timestamp.match(/^(\\d{1,2})-(\\d{1,2})-(\\d{4}) (\\d{1,2}):(\\d{1,2}):(\\d{1,2})$/); let parsedTimestamp = new Date(); if (dateMatch) { const [, day, month, year, hour, minute, second] = dateMatch; parsedTimestamp = new Date( parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second) ); } messages.push({ sessionId: session.id, role: role.trim().toLowerCase(), content: content.trim(), timestamp: parsedTimestamp, order: messageOrder++, }); } } if (messages.length > 0) { await prisma.message.createMany({ data: messages as any, }); console.log(`Added ${messages.length} messages for session ${session.id}`); } } catch (error) { console.error(`Error processing session ${session.id}:`, error); } } } const unprocessedWithMessages = await prisma.session.count({ where: { processed: false, messages: { some: {} } } }); if (unprocessedWithMessages > 0) { console.log(`[Complete Workflow] Processing ${unprocessedWithMessages} sessions`); await processUnprocessedSessions(); } return { message: `Complete workflow finished successfully` }; } catch (error) { console.error('[Complete Workflow] Error:', error); throw error; }} \ No newline at end of file diff --git a/next.config.js b/next.config.js index 48c7615..be02f34 100644 --- a/next.config.js +++ b/next.config.js @@ -8,6 +8,21 @@ const nextConfig = { "127.0.0.1", "localhost" ], + // Disable Turbopack for now due to EISDIR error on Windows + webpack: (config, { isServer }) => { + if (!isServer) { + config.resolve.fallback = { fs: false, net: false, tls: false }; + } + return config; + }, + experimental: { + appDir: true, + serverComponentsExternalPackages: ['@prisma/client', 'bcryptjs'], + // disable the new Turbopack engine + // This is a temporary workaround for the EISDIR error on Windows + // Remove this once the issue is resolved in Next.js or Turbopack + turbopack: false, + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index faa58ed..3296003 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,22 +18,28 @@ "bcryptjs": "^3.0.2", "chart.js": "^4.0.0", "chartjs-plugin-annotation": "^3.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "csv-parse": "^5.5.0", "d3": "^7.9.0", "d3-cloud": "^1.2.7", "i18n-iso-countries": "^7.14.0", "iso-639-1": "^3.1.5", "leaflet": "^1.9.4", - "next": "^15.3.2", + "lucide-react": "^0.523.0", + "next": "^15.3.4", "next-auth": "^4.24.11", "node-cron": "^4.0.7", "node-fetch": "^3.3.2", + "picocolors": "^1.1.1", "react": "^19.1.0", "react-chartjs-2": "^5.0.0", "react-dom": "^19.1.0", "react-leaflet": "^5.0.0", "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0" + "rehype-raw": "^7.0.0", + "source-map-js": "^1.2.1", + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -58,6 +64,7 @@ "tailwindcss": "^4.1.7", "ts-node": "^10.9.2", "tsx": "^4.20.3", + "tw-animate-css": "^1.3.4", "typescript": "^5.0.0" } }, @@ -89,9 +96,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -609,9 +616,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -624,9 +631,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -671,9 +678,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -694,19 +701,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "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/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1242,9 +1262,9 @@ "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", - "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", "dev": true, "license": "MIT", "optional": true, @@ -1255,15 +1275,15 @@ } }, "node_modules/@next/env": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz", - "integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.4.tgz", + "integrity": "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz", - "integrity": "sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.4.tgz", + "integrity": "sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==", "dev": true, "license": "MIT", "dependencies": { @@ -1301,9 +1321,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz", - "integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.4.tgz", + "integrity": "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==", "cpu": [ "arm64" ], @@ -1317,9 +1337,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz", - "integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.4.tgz", + "integrity": "sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==", "cpu": [ "x64" ], @@ -1333,9 +1353,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz", - "integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.4.tgz", + "integrity": "sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==", "cpu": [ "arm64" ], @@ -1349,9 +1369,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz", - "integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.4.tgz", + "integrity": "sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==", "cpu": [ "arm64" ], @@ -1365,9 +1385,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz", - "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.4.tgz", + "integrity": "sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==", "cpu": [ "x64" ], @@ -1381,9 +1401,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz", - "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.4.tgz", + "integrity": "sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==", "cpu": [ "x64" ], @@ -1397,9 +1417,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz", - "integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.4.tgz", + "integrity": "sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==", "cpu": [ "arm64" ], @@ -1413,9 +1433,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz", - "integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.4.tgz", + "integrity": "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==", "cpu": [ "x64" ], @@ -1486,9 +1506,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", - "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", "dev": true, "license": "MIT", "engines": { @@ -1499,13 +1519,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", - "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", + "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.52.0" + "playwright": "1.53.1" }, "bin": { "playwright": "cli.js" @@ -1627,9 +1647,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", - "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", "dev": true, "license": "MIT" }, @@ -1662,9 +1682,9 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.7.tgz", - "integrity": "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1674,13 +1694,13 @@ "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.7" + "tailwindcss": "4.1.11" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.7.tgz", - "integrity": "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1692,24 +1712,24 @@ "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-arm64": "4.1.7", - "@tailwindcss/oxide-darwin-x64": "4.1.7", - "@tailwindcss/oxide-freebsd-x64": "4.1.7", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", - "@tailwindcss/oxide-linux-x64-musl": "4.1.7", - "@tailwindcss/oxide-wasm32-wasi": "4.1.7", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.7.tgz", - "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", "cpu": [ "arm64" ], @@ -1724,9 +1744,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.7.tgz", - "integrity": "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", "cpu": [ "arm64" ], @@ -1741,9 +1761,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.7.tgz", - "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", "cpu": [ "x64" ], @@ -1758,9 +1778,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.7.tgz", - "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", "cpu": [ "x64" ], @@ -1775,9 +1795,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.7.tgz", - "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", "cpu": [ "arm" ], @@ -1792,9 +1812,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.7.tgz", - "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", "cpu": [ "arm64" ], @@ -1809,9 +1829,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.7.tgz", - "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", "cpu": [ "arm64" ], @@ -1826,9 +1846,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.7.tgz", - "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", "cpu": [ "x64" ], @@ -1843,9 +1863,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.7.tgz", - "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", "cpu": [ "x64" ], @@ -1860,9 +1880,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.7.tgz", - "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1881,7 +1901,7 @@ "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", + "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, @@ -1890,9 +1910,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.7.tgz", - "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", "cpu": [ "arm64" ], @@ -1906,10 +1926,10 @@ "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.7.tgz", - "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", + "node_modules/@tailwindcss/oxide/node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", "cpu": [ "x64" ], @@ -1924,17 +1944,17 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.7.tgz", - "integrity": "sha512-88g3qmNZn7jDgrrcp3ZXEQfp9CVox7xjP1HN2TFKI03CltPVd/c61ydn5qJJL8FYunn0OqBaW5HNUga0kmPVvw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", + "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.7", - "@tailwindcss/oxide": "4.1.7", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", - "tailwindcss": "4.1.7" + "tailwindcss": "4.1.11" } }, "node_modules/@tsconfig/node10": { @@ -2261,9 +2281,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -2312,9 +2332,9 @@ "license": "MIT" }, "node_modules/@types/leaflet": { - "version": "1.9.18", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz", - "integrity": "sha512-ht2vsoPjezor5Pmzi5hdsA7F++v5UGq9OlUduWHmMZiuQGIpJ2WS5+Gg9HaAA79gNh1AIPtCqhzejcIZ3lPzXQ==", + "version": "1.9.19", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.19.tgz", + "integrity": "sha512-pB+n2daHcZPF2FDaWa+6B0a0mSDf4dPU35y5iTXsx7x/PzzshiX5atYiS1jlBn43X7XvM8AP+AB26lnSk0J4GA==", "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -2336,9 +2356,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", - "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", + "version": "22.15.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.33.tgz", + "integrity": "sha512-wzoocdnnpSxZ+6CjW4ADCK1jVmd1S/J3ArNWfn8FDDQtRm8dkDg7TA+mvek2wNrfCgwuZxqEOiB9B1XCJ6+dbw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2362,18 +2382,18 @@ } }, "node_modules/@types/react": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", - "integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==", + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2381,23 +2401,23 @@ } }, "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", - "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/type-utils": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2411,15 +2431,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -2427,16 +2447,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", - "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "engines": { @@ -2451,15 +2471,37 @@ "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", - "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1" + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2469,15 +2511,32 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", - "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/utils": "8.32.1", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2494,9 +2553,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", "dev": true, "license": "MIT", "engines": { @@ -2508,14 +2567,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", - "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2535,9 +2596,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2561,16 +2622,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", - "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2585,14 +2646,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", - "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.32.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2603,9 +2664,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "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": { @@ -2621,10 +2682,38 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", - "integrity": "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "cpu": [ "arm64" ], @@ -2636,9 +2725,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz", - "integrity": "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", "cpu": [ "x64" ], @@ -2650,9 +2739,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz", - "integrity": "sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", "cpu": [ "x64" ], @@ -2664,9 +2753,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz", - "integrity": "sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", "cpu": [ "arm" ], @@ -2678,9 +2767,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz", - "integrity": "sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", "cpu": [ "arm" ], @@ -2692,9 +2781,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz", - "integrity": "sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", "cpu": [ "arm64" ], @@ -2706,9 +2795,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz", - "integrity": "sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", "cpu": [ "arm64" ], @@ -2720,9 +2809,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz", - "integrity": "sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", "cpu": [ "ppc64" ], @@ -2734,9 +2823,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz", - "integrity": "sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", "cpu": [ "riscv64" ], @@ -2748,9 +2837,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz", - "integrity": "sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", "cpu": [ "riscv64" ], @@ -2762,9 +2851,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz", - "integrity": "sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", "cpu": [ "s390x" ], @@ -2776,9 +2865,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz", - "integrity": "sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", "cpu": [ "x64" ], @@ -2790,9 +2879,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz", - "integrity": "sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", "cpu": [ "x64" ], @@ -2804,9 +2893,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz", - "integrity": "sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", "cpu": [ "wasm32" ], @@ -2814,16 +2903,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" + "@napi-rs/wasm-runtime": "^0.2.11" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz", - "integrity": "sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", "cpu": [ "arm64" ], @@ -2835,9 +2924,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz", - "integrity": "sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", "cpu": [ "ia32" ], @@ -2849,9 +2938,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz", - "integrity": "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", "cpu": [ "x64" ], @@ -2863,9 +2952,9 @@ ] }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "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": { @@ -2973,18 +3062,20 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3199,9 +3290,9 @@ } }, "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": { @@ -3293,9 +3384,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -3380,9 +3471,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", - "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -3410,12 +3501,33 @@ "node": ">=18" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3641,6 +3753,12 @@ "d3-dispatch": "^1.0.3" } }, + "node_modules/d3-cloud/node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", + "license": "BSD-3-Clause" + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -3675,10 +3793,13 @@ } }, "node_modules/d3-dispatch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", - "license": "BSD-3-Clause" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/d3-drag": { "version": "3.0.0", @@ -3949,15 +4070,6 @@ "node": ">=12" } }, - "node_modules/d3/node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -4046,9 +4158,9 @@ } }, "node_modules/decode-named-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", - "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -4167,6 +4279,19 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4189,9 +4314,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4203,9 +4328,10 @@ } }, "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4215,9 +4341,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -4225,18 +4351,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -4248,21 +4374,24 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", + "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -4271,7 +4400,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -4439,19 +4568,19 @@ } }, "node_modules/eslint": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", - "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.27.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4463,9 +4592,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "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", @@ -4500,13 +4629,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.2.tgz", - "integrity": "sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.4.tgz", + "integrity": "sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.3.2", + "@next/eslint-plugin-next": "15.3.4", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -4585,9 +4714,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, "license": "MIT", "dependencies": { @@ -4613,30 +4742,30 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -4656,19 +4785,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4710,14 +4826,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz", - "integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", + "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", "dev": true, "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.0" + "synckit": "^0.11.7" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4786,19 +4902,6 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -4828,9 +4931,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "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": { @@ -4858,9 +4961,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "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": { @@ -4871,15 +4974,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "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.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4889,9 +4992,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "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": { @@ -5135,14 +5238,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "license": "MIT", "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": { @@ -5349,9 +5453,9 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { @@ -5495,6 +5599,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -5533,6 +5643,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -5560,6 +5676,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", @@ -5997,6 +6119,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6708,6 +6843,15 @@ "node": ">=10" } }, + "node_modules/lucide-react": { + "version": "0.523.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz", + "integrity": "sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -6743,19 +6887,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/markdownlint": { "version": "0.38.0", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.38.0.tgz", @@ -6850,6 +6981,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -6892,6 +7029,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", @@ -6966,6 +7109,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/mdast-util-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", @@ -7679,12 +7828,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz", - "integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==", + "version": "15.3.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz", + "integrity": "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==", "license": "MIT", "dependencies": { - "@next/env": "15.3.2", + "@next/env": "15.3.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", @@ -7699,14 +7848,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.3.2", - "@next/swc-darwin-x64": "15.3.2", - "@next/swc-linux-arm64-gnu": "15.3.2", - "@next/swc-linux-arm64-musl": "15.3.2", - "@next/swc-linux-x64-gnu": "15.3.2", - "@next/swc-linux-x64-musl": "15.3.2", - "@next/swc-win32-arm64-msvc": "15.3.2", - "@next/swc-win32-x64-msvc": "15.3.2", + "@next/swc-darwin-arm64": "15.3.4", + "@next/swc-darwin-x64": "15.3.4", + "@next/swc-linux-arm64-gnu": "15.3.4", + "@next/swc-linux-arm64-musl": "15.3.4", + "@next/swc-linux-x64-gnu": "15.3.4", + "@next/swc-linux-x64-musl": "15.3.4", + "@next/swc-win32-arm64-msvc": "15.3.4", + "@next/swc-win32-x64-msvc": "15.3.4", "sharp": "^0.34.1" }, "peerDependencies": { @@ -7793,9 +7942,9 @@ } }, "node_modules/node-cron": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.0.7.tgz", - "integrity": "sha512-A37UUDpxRT/kWanELr/oMayCWQFk9Zx9BEUoXrAKuKwKzH4XuAX+vMixMBPkgZBkADgJwXv91w5cMRTNSVP/mA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.1.1.tgz", + "integrity": "sha512-oJj9CYV7teeCVs+y2Efi5IQ4FGmAYbsXQOehc1AGLlwteec8pC7DjBCUzSyRQ0LYa+CRCgmD+vtlWQcnPpXowA==", "license": "ISC", "engines": { "node": ">=6.0.0" @@ -8101,12 +8250,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -8119,6 +8262,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8179,13 +8334,13 @@ } }, "node_modules/playwright": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", - "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", + "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.52.0" + "playwright-core": "1.53.1" }, "bin": { "playwright": "cli.js" @@ -8198,9 +8353,9 @@ } }, "node_modules/playwright-core": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", - "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", + "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -8221,9 +8376,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -8241,7 +8396,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8250,9 +8405,9 @@ } }, "node_modules/preact": { - "version": "10.26.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", - "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", + "version": "10.26.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz", + "integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==", "license": "MIT", "funding": { "type": "opencollective", @@ -8282,9 +8437,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz", + "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==", "dev": true, "license": "MIT", "bin": { @@ -9008,6 +9163,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -9167,18 +9336,18 @@ } }, "node_modules/style-to-js": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", - "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.8" + "style-to-object": "1.0.9" } }, "node_modules/style-to-object": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", - "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", "license": "MIT", "dependencies": { "inline-style-parser": "0.2.4" @@ -9234,9 +9403,9 @@ } }, "node_modules/synckit": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz", - "integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", "dev": true, "license": "MIT", "dependencies": { @@ -9249,10 +9418,20 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.7.tgz", - "integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "dev": true, "license": "MIT" }, @@ -9295,9 +9474,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9312,9 +9491,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -9483,6 +9662,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/tw-animate-css": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.4.tgz", + "integrity": "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -9652,6 +9841,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unified/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -9665,6 +9860,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unist-util-position": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", @@ -9678,6 +9879,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -9691,6 +9898,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unist-util-visit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", @@ -9720,37 +9933,51 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/unrs-resolver": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", - "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.2.4" }, "funding": { - "url": "https://github.com/sponsors/JounQin" + "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.2", - "@unrs/resolver-binding-darwin-x64": "1.7.2", - "@unrs/resolver-binding-freebsd-x64": "1.7.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-musl": "1.7.2", - "@unrs/resolver-binding-wasm32-wasi": "1.7.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.2" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" } }, "node_modules/uri-js": { @@ -9807,6 +10034,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/vfile-message": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", @@ -9821,6 +10054,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", diff --git a/package.json b/package.json index aa4bafb..2bdf658 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "build": "next build", - "dev": "next dev --turbopack", + "dev": "next dev", "dev:with-server": "tsx server.ts", "format": "npx prettier --write .", "format:check": "npx prettier --check .", @@ -31,22 +31,28 @@ "bcryptjs": "^3.0.2", "chart.js": "^4.0.0", "chartjs-plugin-annotation": "^3.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "csv-parse": "^5.5.0", "d3": "^7.9.0", "d3-cloud": "^1.2.7", "i18n-iso-countries": "^7.14.0", "iso-639-1": "^3.1.5", "leaflet": "^1.9.4", - "next": "^15.3.2", + "lucide-react": "^0.523.0", + "next": "^15.3.4", "next-auth": "^4.24.11", "node-cron": "^4.0.7", "node-fetch": "^3.3.2", + "picocolors": "^1.1.1", "react": "^19.1.0", "react-chartjs-2": "^5.0.0", "react-dom": "^19.1.0", "react-leaflet": "^5.0.0", "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0" + "rehype-raw": "^7.0.0", + "source-map-js": "^1.2.1", + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -71,6 +77,7 @@ "tailwindcss": "^4.1.7", "ts-node": "^10.9.2", "tsx": "^4.20.3", + "tw-animate-css": "^1.3.4", "typescript": "^5.0.0" }, "prettier": { diff --git a/pages/api/admin/refresh-sessions.ts b/pages/api/admin/refresh-sessions.ts deleted file mode 100644 index c16d060..0000000 --- a/pages/api/admin/refresh-sessions.ts +++ /dev/null @@ -1,315 +0,0 @@ -// API route to refresh (fetch+parse+update) session data for a company -import { NextApiRequest, NextApiResponse } from "next"; -import { fetchAndParseCsv } from "../../../lib/csvFetcher"; -import { prisma } from "../../../lib/prisma"; -import { processUnprocessedSessions } from "../../../lib/processingSchedulerNoCron"; -import { exec } from "child_process"; -import { promisify } from "util"; - -const execAsync = promisify(exec); - -/** - * Triggers the complete workflow: fetch transcripts + process all sessions - */ -async function triggerCompleteWorkflow(): Promise<{ message: string }> { - try { - // Step 1: Fetch missing transcripts - const sessionsWithoutMessages = await prisma.session.count({ - where: { - messages: { none: {} }, - fullTranscriptUrl: { not: null } - } - }); - - if (sessionsWithoutMessages > 0) { - console.log(`[Complete Workflow] Fetching transcripts for ${sessionsWithoutMessages} sessions`); - - // Get sessions that have fullTranscriptUrl but no messages - const sessionsToProcess = await prisma.session.findMany({ - where: { - AND: [ - { fullTranscriptUrl: { not: null } }, - { messages: { none: {} } }, - ], - }, - include: { - company: true, - }, - take: 20, // Process in batches - }); - - for (const session of sessionsToProcess) { - try { - if (!session.fullTranscriptUrl) continue; - - // Fetch transcript content - const transcriptContent = await fetchTranscriptContent( - session.fullTranscriptUrl, - session.company.csvUsername || undefined, - session.company.csvPassword || undefined - ); - - if (!transcriptContent) { - console.log(`No transcript content for session ${session.id}`); - continue; - } - - // Parse transcript into messages - const lines = transcriptContent.split("\n").filter((line) => line.trim()); - const messages: Array<{ - sessionId: string; - role: string; - content: string; - timestamp: Date; - order: number; - }> = []; - let messageOrder = 0; - const currentTimestamp = new Date(); - - for (const line of lines) { - // Try format: [DD-MM-YYYY HH:MM:SS] Role: Content - const timestampMatch = line.match(/^\[([^\]]+)\]\s*([^:]+):\s*(.+)$/); - - if (timestampMatch) { - const [, timestamp, role, content] = timestampMatch; - const dateMatch = timestamp.match(/^(\d{1,2})-(\d{1,2})-(\d{4}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/); - let parsedTimestamp = new Date(); - - if (dateMatch) { - const [, day, month, year, hour, minute, second] = dateMatch; - parsedTimestamp = new Date( - parseInt(year), - parseInt(month) - 1, - parseInt(day), - parseInt(hour), - parseInt(minute), - parseInt(second) - ); - } - - messages.push({ - sessionId: session.id, - role: role.trim().toLowerCase(), - content: content.trim(), - timestamp: parsedTimestamp, - order: messageOrder++, - }); - } - } - - if (messages.length > 0) { - // Save messages to database - await prisma.message.createMany({ - data: messages as any, // Type assertion needed due to Prisma types - }); - console.log(`Added ${messages.length} messages for session ${session.id}`); - } - } catch (error) { - console.error(`Error processing session ${session.id}:`, error); - } - } - } - - // Step 2: Process all unprocessed sessions - const unprocessedWithMessages = await prisma.session.count({ - where: { - processed: false, - messages: { some: {} } - } - }); - - if (unprocessedWithMessages > 0) { - console.log(`[Complete Workflow] Processing ${unprocessedWithMessages} sessions`); - await processUnprocessedSessions(); - } - - return { message: `Complete workflow finished successfully` }; - } catch (error) { - console.error('[Complete Workflow] Error:', error); - throw error; - } -} - -interface SessionCreateData { - id: string; - startTime: Date; - companyId: string; - sessionId?: string; - [key: string]: unknown; -} - -/** - * Fetches transcript content from a URL - * @param url The URL to fetch the transcript from - * @param username Optional username for authentication - * @param password Optional password for authentication - * @returns The transcript content or null if fetching fails - */ -async function fetchTranscriptContent( - url: string, - username?: string, - password?: string -): Promise { - try { - const authHeader = - username && password - ? "Basic " + Buffer.from(`${username}:${password}`).toString("base64") - : undefined; - - const response = await fetch(url, { - headers: authHeader ? { Authorization: authHeader } : {}, - }); - - if (!response.ok) { - process.stderr.write( - `Error fetching transcript: ${response.statusText}\n` - ); - return null; - } - return await response.text(); - } catch (error) { - process.stderr.write(`Failed to fetch transcript: ${error}\n`); - return null; - } -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - // Check if this is a POST request - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed" }); - } - - // Get companyId from body or query - let { companyId } = req.body; - - if (!companyId) { - // Try to get user from prisma based on session cookie - try { - const session = await prisma.session.findFirst({ - orderBy: { createdAt: "desc" }, - where: { - /* Add session check criteria here */ - }, - }); - - if (session) { - companyId = session.companyId; - } - } catch (error) { - // Log error for server-side debugging - const errorMessage = - error instanceof Error ? error.message : String(error); - // Use a server-side logging approach instead of console - process.stderr.write(`Error fetching session: ${errorMessage}\n`); - } - } - - if (!companyId) { - return res.status(400).json({ error: "Company ID is required" }); - } - - const company = await prisma.company.findUnique({ where: { id: companyId } }); - if (!company) return res.status(404).json({ error: "Company not found" }); - - try { - const sessions = await fetchAndParseCsv( - company.csvUrl, - company.csvUsername as string | undefined, - company.csvPassword as string | undefined - ); - - // Only add sessions that don't already exist in the database - for (const session of sessions) { - const sessionData: SessionCreateData = { - ...session, - companyId: company.id, - id: - session.id || - session.sessionId || - `sess_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`, - // Ensure startTime is not undefined - startTime: session.startTime || new Date(), - }; - - // Validate dates to prevent "Invalid Date" errors - const startTime = - sessionData.startTime instanceof Date && - !isNaN(sessionData.startTime.getTime()) - ? sessionData.startTime - : new Date(); - - const endTime = - session.endTime instanceof Date && !isNaN(session.endTime.getTime()) - ? session.endTime - : new Date(); - - // Note: transcriptContent field was removed from schema - // Transcript content can be fetched on-demand from fullTranscriptUrl - - // Check if the session already exists - const existingSession = await prisma.session.findUnique({ - where: { id: sessionData.id }, - }); - - if (existingSession) { - // Skip this session as it already exists - continue; - } - - // Only include fields that are properly typed for Prisma - await prisma.session.create({ - data: { - id: sessionData.id, - companyId: sessionData.companyId, - startTime: startTime, - endTime: endTime, - ipAddress: session.ipAddress || null, - country: session.country || null, - language: session.language || null, - messagesSent: - typeof session.messagesSent === "number" ? session.messagesSent : 0, - sentiment: - typeof session.sentiment === "number" ? session.sentiment : null, - escalated: - typeof session.escalated === "boolean" ? session.escalated : null, - forwardedHr: - typeof session.forwardedHr === "boolean" - ? session.forwardedHr - : null, - fullTranscriptUrl: session.fullTranscriptUrl || null, - avgResponseTime: - typeof session.avgResponseTime === "number" - ? session.avgResponseTime - : null, - tokens: typeof session.tokens === "number" ? session.tokens : null, - tokensEur: - typeof session.tokensEur === "number" ? session.tokensEur : null, - category: session.category || null, - initialMsg: session.initialMsg || null, - }, - }); - } - - // After importing sessions, automatically trigger complete workflow (fetch transcripts + process) - // This runs in the background without blocking the response - triggerCompleteWorkflow() - .then((result) => { - console.log(`[Refresh Sessions] Complete workflow finished: ${result.message}`); - }) - .catch((error) => { - console.error(`[Refresh Sessions] Complete workflow failed:`, error); - }); - - res.json({ - ok: true, - imported: sessions.length, - message: "Sessions imported and complete processing workflow started automatically" - }); - } catch (e) { - const error = e instanceof Error ? e.message : "An unknown error occurred"; - res.status(500).json({ error }); - } -} diff --git a/pages/api/admin/trigger-processing.ts b/pages/api/admin/trigger-processing.ts deleted file mode 100644 index 75b5b14..0000000 --- a/pages/api/admin/trigger-processing.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth"; -import { authOptions } from "../auth/[...nextauth]"; -import { prisma } from "../../../lib/prisma"; -import { processUnprocessedSessions } from "../../../lib/processingSchedulerNoCron"; - -interface SessionUser { - email: string; - name?: string; -} - -interface SessionData { - user: SessionUser; -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "POST") { - return res.status(405).json({ error: "Method not allowed" }); - } - - const session = (await getServerSession( - req, - res, - authOptions - )) as SessionData | null; - - if (!session?.user) { - return res.status(401).json({ error: "Not logged in" }); - } - - const user = await prisma.user.findUnique({ - where: { email: session.user.email }, - include: { company: true }, - }); - - if (!user) { - return res.status(401).json({ error: "No user found" }); - } - - // Check if user has admin role - if (user.role !== "admin") { - return res.status(403).json({ error: "Admin access required" }); - } - - try { - // Get optional parameters from request body - const { batchSize, maxConcurrency } = req.body; - - // Validate parameters - const validatedBatchSize = - batchSize && batchSize > 0 ? parseInt(batchSize) : null; - const validatedMaxConcurrency = - maxConcurrency && maxConcurrency > 0 ? parseInt(maxConcurrency) : 5; - - // Check how many unprocessed sessions exist - const unprocessedCount = await prisma.session.count({ - where: { - companyId: user.companyId, - processed: false, - messages: { some: {} }, // Must have messages - }, - }); - - if (unprocessedCount === 0) { - return res.json({ - success: true, - message: "No unprocessed sessions found", - unprocessedCount: 0, - processedCount: 0, - }); - } - - // Start processing (this will run asynchronously) - const startTime = Date.now(); - - // Note: We're calling the function but not awaiting it to avoid timeout - // The processing will continue in the background - processUnprocessedSessions(validatedBatchSize || undefined, validatedMaxConcurrency) - .then(() => { - console.log( - `[Manual Trigger] Processing completed for company ${user.companyId}` - ); - }) - .catch((error) => { - console.error( - `[Manual Trigger] Processing failed for company ${user.companyId}:`, - error - ); - }); - - return res.json({ - success: true, - message: `Started processing ${unprocessedCount} unprocessed sessions`, - unprocessedCount, - batchSize: validatedBatchSize || unprocessedCount, - maxConcurrency: validatedMaxConcurrency, - startedAt: new Date().toISOString(), - }); - } catch (error) { - console.error("[Manual Trigger] Error:", error); - return res.status(500).json({ - error: "Failed to trigger processing", - details: error instanceof Error ? error.message : String(error), - }); - } -} diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts deleted file mode 100644 index 348c679..0000000 --- a/pages/api/auth/[...nextauth].ts +++ /dev/null @@ -1,104 +0,0 @@ -import NextAuth, { NextAuthOptions } from "next-auth"; -import CredentialsProvider from "next-auth/providers/credentials"; -import { prisma } from "../../../lib/prisma"; -import bcrypt from "bcryptjs"; - -// Define the shape of the JWT token -declare module "next-auth/jwt" { - interface JWT { - companyId: string; - role: string; - } -} - -// Define the shape of the session object -declare module "next-auth" { - interface Session { - user: { - id?: string; - name?: string; - email?: string; - image?: string; - companyId: string; - role: string; - }; - } - - interface User { - id: string; - email: string; - companyId: string; - role: string; - } -} - -export const authOptions: NextAuthOptions = { - providers: [ - CredentialsProvider({ - name: "Credentials", - credentials: { - email: { label: "Email", type: "text" }, - password: { label: "Password", type: "password" }, - }, - async authorize(credentials) { - if (!credentials?.email || !credentials?.password) { - return null; - } - - const user = await prisma.user.findUnique({ - where: { email: credentials.email }, - }); - - if (!user) return null; - - const valid = await bcrypt.compare(credentials.password, user.password); - if (!valid) return null; - - return { - id: user.id, - email: user.email, - companyId: user.companyId, - role: user.role, - }; - }, - }), - ], - session: { - strategy: "jwt", - maxAge: 30 * 24 * 60 * 60, // 30 days - }, - cookies: { - sessionToken: { - name: `next-auth.session-token`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: process.env.NODE_ENV === "production", - }, - }, - }, - callbacks: { - async jwt({ token, user }) { - if (user) { - token.companyId = user.companyId; - token.role = user.role; - } - return token; - }, - async session({ session, token }) { - if (token && session.user) { - session.user.companyId = token.companyId; - session.user.role = token.role; - } - return session; - }, - }, - pages: { - signIn: "/login", - }, - secret: process.env.NEXTAUTH_SECRET, - debug: process.env.NODE_ENV === "development", -}; - -export default NextAuth(authOptions); diff --git a/pages/api/dashboard/config.ts b/pages/api/dashboard/config.ts deleted file mode 100644 index 55a1345..0000000 --- a/pages/api/dashboard/config.ts +++ /dev/null @@ -1,36 +0,0 @@ -// API endpoint: update company CSV URL config -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth"; -import { prisma } from "../../../lib/prisma"; -import { authOptions } from "../auth/[...nextauth]"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const session = await getServerSession(req, res, authOptions); - if (!session?.user) return res.status(401).json({ error: "Not logged in" }); - - const user = await prisma.user.findUnique({ - where: { email: session.user.email as string }, - }); - - if (!user) return res.status(401).json({ error: "No user" }); - - if (req.method === "POST") { - const { csvUrl } = req.body; - await prisma.company.update({ - where: { id: user.companyId }, - data: { csvUrl }, - }); - res.json({ ok: true }); - } else if (req.method === "GET") { - // Get company data - const company = await prisma.company.findUnique({ - where: { id: user.companyId }, - }); - res.json({ company }); - } else { - res.status(405).end(); - } -} diff --git a/pages/api/dashboard/metrics.ts b/pages/api/dashboard/metrics.ts deleted file mode 100644 index 819fe43..0000000 --- a/pages/api/dashboard/metrics.ts +++ /dev/null @@ -1,118 +0,0 @@ -// API endpoint: return metrics for current company -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth"; -import { prisma } from "../../../lib/prisma"; -import { sessionMetrics } from "../../../lib/metrics"; -import { authOptions } from "../auth/[...nextauth]"; -import { ChatSession } from "../../../lib/types"; // Import ChatSession - -interface SessionUser { - email: string; - name?: string; -} - -interface SessionData { - user: SessionUser; -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const session = (await getServerSession( - req, - res, - authOptions - )) as SessionData | null; - if (!session?.user) return res.status(401).json({ error: "Not logged in" }); - - const user = await prisma.user.findUnique({ - where: { email: session.user.email }, - include: { company: true }, - }); - - if (!user) return res.status(401).json({ error: "No user" }); - - // Get date range from query parameters - const { startDate, endDate } = req.query; - - // Build where clause with optional date filtering and only processed sessions - const whereClause: any = { - companyId: user.companyId, - processed: true, // Only show processed sessions in dashboard - }; - - if (startDate && endDate) { - whereClause.startTime = { - gte: new Date(startDate as string), - lte: new Date((endDate as string) + "T23:59:59.999Z"), // Include full end date - }; - } - - const prismaSessions = await prisma.session.findMany({ - where: whereClause, - include: { - messages: true, // Include messages for question extraction - }, - }); - - // Convert Prisma sessions to ChatSession[] type for sessionMetrics - const chatSessions: ChatSession[] = prismaSessions.map((ps) => ({ - id: ps.id, // Map Prisma's id to ChatSession.id - sessionId: ps.id, // Map Prisma's id to ChatSession.sessionId - companyId: ps.companyId, - startTime: new Date(ps.startTime), // Ensure startTime is a Date object - endTime: ps.endTime ? new Date(ps.endTime) : null, // Ensure endTime is a Date object or null - transcriptContent: "", // Session model doesn't have transcriptContent field - createdAt: new Date(ps.createdAt), // Map Prisma's createdAt - updatedAt: new Date(ps.createdAt), // Use createdAt for updatedAt as Session model doesn't have updatedAt - category: ps.category || undefined, - language: ps.language || undefined, - country: ps.country || undefined, - ipAddress: ps.ipAddress || undefined, - sentiment: ps.sentiment === null ? undefined : ps.sentiment, - messagesSent: ps.messagesSent === null ? undefined : ps.messagesSent, // Handle null messagesSent - avgResponseTime: - ps.avgResponseTime === null ? undefined : ps.avgResponseTime, - tokens: ps.tokens === null ? undefined : ps.tokens, - tokensEur: ps.tokensEur === null ? undefined : ps.tokensEur, - escalated: ps.escalated || false, - forwardedHr: ps.forwardedHr || false, - initialMsg: ps.initialMsg || undefined, - fullTranscriptUrl: ps.fullTranscriptUrl || undefined, - questions: ps.questions || undefined, // Include questions field - summary: ps.summary || undefined, // Include summary field - messages: ps.messages || [], // Include messages for question extraction - // userId is missing in Prisma Session model, assuming it's not strictly needed for metrics or can be null - userId: undefined, // Or some other default/mapping if available - })); - - // Pass company config to metrics - const companyConfigForMetrics = { - sentimentAlert: - user.company.sentimentAlert === null - ? undefined - : user.company.sentimentAlert, - }; - - const metrics = sessionMetrics(chatSessions, companyConfigForMetrics); - - // Calculate date range from sessions - let dateRange: { minDate: string; maxDate: string } | null = null; - if (prismaSessions.length > 0) { - const dates = prismaSessions - .map((s) => new Date(s.startTime)) - .sort((a, b) => a.getTime() - b.getTime()); - dateRange = { - minDate: dates[0].toISOString().split("T")[0], // First session date - maxDate: dates[dates.length - 1].toISOString().split("T")[0], // Last session date - }; - } - - res.json({ - metrics, - csvUrl: user.company.csvUrl, - company: user.company, - dateRange, - }); -} diff --git a/pages/api/dashboard/session-filter-options.ts b/pages/api/dashboard/session-filter-options.ts deleted file mode 100644 index 1277098..0000000 --- a/pages/api/dashboard/session-filter-options.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../auth/[...nextauth]"; -import { prisma } from "../../../lib/prisma"; -import { SessionFilterOptions } from "../../../lib/types"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse< - SessionFilterOptions | { error: string; details?: string } - > -) { - if (req.method !== "GET") { - return res.status(405).json({ error: "Method not allowed" }); - } - - const authSession = await getServerSession(req, res, authOptions); - - if (!authSession || !authSession.user?.companyId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const companyId = authSession.user.companyId; - - try { - const categories = await prisma.session.findMany({ - where: { - companyId, - category: { - not: null, // Ensure category is not null - }, - }, - distinct: ["category"], - select: { - category: true, - }, - orderBy: { - category: "asc", - }, - }); - - const languages = await prisma.session.findMany({ - where: { - companyId, - language: { - not: null, // Ensure language is not null - }, - }, - distinct: ["language"], - select: { - language: true, - }, - orderBy: { - language: "asc", - }, - }); - - const distinctCategories = categories - .map((s) => s.category) - .filter(Boolean) as string[]; // Filter out any nulls and assert as string[] - const distinctLanguages = languages - .map((s) => s.language) - .filter(Boolean) as string[]; // Filter out any nulls and assert as string[] - - return res - .status(200) - .json({ categories: distinctCategories, languages: distinctLanguages }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "An unknown error occurred"; - return res.status(500).json({ - error: "Failed to fetch filter options", - details: errorMessage, - }); - } -} diff --git a/pages/api/dashboard/session/[id].ts b/pages/api/dashboard/session/[id].ts deleted file mode 100644 index 6fdedc4..0000000 --- a/pages/api/dashboard/session/[id].ts +++ /dev/null @@ -1,86 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { prisma } from "../../../../lib/prisma"; -import { ChatSession } from "../../../../lib/types"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "GET") { - return res.status(405).json({ error: "Method not allowed" }); - } - - const { id } = req.query; - - if (!id || typeof id !== "string") { - return res.status(400).json({ error: "Session ID is required" }); - } - - try { - const prismaSession = await prisma.session.findUnique({ - where: { id }, - include: { - messages: { - orderBy: { order: "asc" }, - }, - }, - }); - - if (!prismaSession) { - return res.status(404).json({ error: "Session not found" }); - } - - // Map Prisma session object to ChatSession type - const session: ChatSession = { - // Spread prismaSession to include all its properties - ...prismaSession, - // Override properties that need conversion or specific mapping - id: prismaSession.id, // ChatSession.id from Prisma.Session.id - sessionId: prismaSession.id, // ChatSession.sessionId from Prisma.Session.id - startTime: new Date(prismaSession.startTime), - endTime: prismaSession.endTime ? new Date(prismaSession.endTime) : null, - createdAt: new Date(prismaSession.createdAt), - // Prisma.Session does not have an `updatedAt` field. We'll use `createdAt` as a fallback. - // Or, if your business logic implies an update timestamp elsewhere, use that. - updatedAt: new Date(prismaSession.createdAt), // Fallback to createdAt - // Prisma.Session does not have a `userId` field. - userId: null, // Explicitly set to null or map if available from another source - // Ensure nullable fields from Prisma are correctly mapped to ChatSession's optional or nullable fields - category: prismaSession.category ?? null, - language: prismaSession.language ?? null, - country: prismaSession.country ?? null, - ipAddress: prismaSession.ipAddress ?? null, - sentiment: prismaSession.sentiment ?? null, - sentimentCategory: prismaSession.sentimentCategory ?? null, // New field - messagesSent: prismaSession.messagesSent ?? undefined, // Use undefined if ChatSession expects number | undefined - avgResponseTime: prismaSession.avgResponseTime ?? null, - escalated: prismaSession.escalated ?? undefined, - forwardedHr: prismaSession.forwardedHr ?? undefined, - tokens: prismaSession.tokens ?? undefined, - tokensEur: prismaSession.tokensEur ?? undefined, - initialMsg: prismaSession.initialMsg ?? undefined, - fullTranscriptUrl: prismaSession.fullTranscriptUrl ?? null, - processed: prismaSession.processed ?? null, // New field - questions: prismaSession.questions ?? null, // New field - summary: prismaSession.summary ?? null, // New field - messages: - prismaSession.messages?.map((msg) => ({ - id: msg.id, - sessionId: msg.sessionId, - timestamp: new Date(msg.timestamp), - role: msg.role, - content: msg.content, - order: msg.order, - createdAt: new Date(msg.createdAt), - })) ?? [], // New field - parsed messages - }; - - return res.status(200).json({ session }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "An unknown error occurred"; - return res - .status(500) - .json({ error: "Failed to fetch session", details: errorMessage }); - } -} diff --git a/pages/api/dashboard/sessions.ts b/pages/api/dashboard/sessions.ts deleted file mode 100644 index 625cd11..0000000 --- a/pages/api/dashboard/sessions.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../auth/[...nextauth]"; -import { prisma } from "../../../lib/prisma"; -import { - ChatSession, - SessionApiResponse, - SessionQuery, -} from "../../../lib/types"; -import { Prisma } from "@prisma/client"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "GET") { - return res.status(405).json({ error: "Method not allowed" }); - } - - const authSession = await getServerSession(req, res, authOptions); - - if (!authSession || !authSession.user?.companyId) { - return res.status(401).json({ error: "Unauthorized" }); - } - - const companyId = authSession.user.companyId; - const { - searchTerm, - category, - language, - startDate, - endDate, - sortKey, - sortOrder, - page: queryPage, - pageSize: queryPageSize, - } = req.query as SessionQuery; - - const page = Number(queryPage) || 1; - const pageSize = Number(queryPageSize) || 10; - - try { - const whereClause: Prisma.SessionWhereInput = { companyId }; - - // Search Term - if ( - searchTerm && - typeof searchTerm === "string" && - searchTerm.trim() !== "" - ) { - const searchConditions = [ - { id: { contains: searchTerm } }, - { category: { contains: searchTerm } }, - { initialMsg: { contains: searchTerm } }, - { transcriptContent: { contains: searchTerm } }, - ]; - whereClause.OR = searchConditions; - } - - // Category Filter - if (category && typeof category === "string" && category.trim() !== "") { - whereClause.category = category; - } - - // Language Filter - if (language && typeof language === "string" && language.trim() !== "") { - whereClause.language = language; - } - - // Date Range Filter - if (startDate && typeof startDate === "string") { - whereClause.startTime = { - ...((whereClause.startTime as object) || {}), - gte: new Date(startDate), - }; - } - if (endDate && typeof endDate === "string") { - const inclusiveEndDate = new Date(endDate); - inclusiveEndDate.setDate(inclusiveEndDate.getDate() + 1); - whereClause.startTime = { - ...((whereClause.startTime as object) || {}), - lt: inclusiveEndDate, - }; - } - - // Sorting - const validSortKeys: { [key: string]: string } = { - startTime: "startTime", - category: "category", - language: "language", - sentiment: "sentiment", - messagesSent: "messagesSent", - avgResponseTime: "avgResponseTime", - }; - - let orderByCondition: - | Prisma.SessionOrderByWithRelationInput - | Prisma.SessionOrderByWithRelationInput[]; - - const primarySortField = - sortKey && typeof sortKey === "string" && validSortKeys[sortKey] - ? validSortKeys[sortKey] - : "startTime"; // Default to startTime field if sortKey is invalid/missing - - const primarySortOrder = - sortOrder === "asc" || sortOrder === "desc" ? sortOrder : "desc"; // Default to desc order - - if (primarySortField === "startTime") { - // If sorting by startTime, it's the only sort criteria - orderByCondition = { [primarySortField]: primarySortOrder }; - } else { - // If sorting by another field, use startTime: "desc" as secondary sort - orderByCondition = [ - { [primarySortField]: primarySortOrder }, - { startTime: "desc" }, - ]; - } - // Note: If sortKey was initially undefined or invalid, primarySortField defaults to "startTime", - // and primarySortOrder defaults to "desc". This makes orderByCondition = { startTime: "desc" }, - // which is the correct overall default sort. - - const prismaSessions = await prisma.session.findMany({ - where: whereClause, - orderBy: orderByCondition, - skip: (page - 1) * pageSize, - take: pageSize, - }); - - const totalSessions = await prisma.session.count({ where: whereClause }); - - const sessions: ChatSession[] = prismaSessions.map((ps) => ({ - id: ps.id, - sessionId: ps.id, - companyId: ps.companyId, - startTime: new Date(ps.startTime), - endTime: ps.endTime ? new Date(ps.endTime) : null, - createdAt: new Date(ps.createdAt), - updatedAt: new Date(ps.createdAt), - userId: null, - category: ps.category ?? null, - language: ps.language ?? null, - country: ps.country ?? null, - ipAddress: ps.ipAddress ?? null, - sentiment: ps.sentiment ?? null, - messagesSent: ps.messagesSent ?? undefined, - avgResponseTime: ps.avgResponseTime ?? null, - escalated: ps.escalated ?? undefined, - forwardedHr: ps.forwardedHr ?? undefined, - tokens: ps.tokens ?? undefined, - tokensEur: ps.tokensEur ?? undefined, - initialMsg: ps.initialMsg ?? undefined, - fullTranscriptUrl: ps.fullTranscriptUrl ?? null, - transcriptContent: ps.transcriptContent ?? null, - })); - - return res.status(200).json({ sessions, totalSessions }); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "An unknown error occurred"; - return res - .status(500) - .json({ error: "Failed to fetch sessions", details: errorMessage }); - } -} diff --git a/pages/api/dashboard/settings.ts b/pages/api/dashboard/settings.ts deleted file mode 100644 index e0b6c0d..0000000 --- a/pages/api/dashboard/settings.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { getServerSession } from "next-auth"; -import { prisma } from "../../../lib/prisma"; -import { authOptions } from "../auth/[...nextauth]"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const session = await getServerSession(req, res, authOptions); - if (!session?.user || session.user.role !== "admin") - return res.status(403).json({ error: "Forbidden" }); - - const user = await prisma.user.findUnique({ - where: { email: session.user.email as string }, - }); - - if (!user) return res.status(401).json({ error: "No user" }); - - if (req.method === "POST") { - const { csvUrl, csvUsername, csvPassword, sentimentThreshold } = req.body; - await prisma.company.update({ - where: { id: user.companyId }, - data: { - csvUrl, - csvUsername, - ...(csvPassword ? { csvPassword } : {}), - sentimentAlert: sentimentThreshold - ? parseFloat(sentimentThreshold) - : null, - }, - }); - res.json({ ok: true }); - } else { - res.status(405).end(); - } -} diff --git a/pages/api/dashboard/users.ts b/pages/api/dashboard/users.ts deleted file mode 100644 index 97ef027..0000000 --- a/pages/api/dashboard/users.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import crypto from "crypto"; -import { getServerSession } from "next-auth"; -import { prisma } from "../../../lib/prisma"; -import bcrypt from "bcryptjs"; -import { authOptions } from "../auth/[...nextauth]"; -// User type from prisma is used instead of the one in lib/types - -interface UserBasicInfo { - id: string; - email: string; - role: string; -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const session = await getServerSession(req, res, authOptions); - if (!session?.user || session.user.role !== "admin") - return res.status(403).json({ error: "Forbidden" }); - - const user = await prisma.user.findUnique({ - where: { email: session.user.email as string }, - }); - - if (!user) return res.status(401).json({ error: "No user" }); - - if (req.method === "GET") { - const users = await prisma.user.findMany({ - where: { companyId: user.companyId }, - }); - - const mappedUsers: UserBasicInfo[] = users.map((u) => ({ - id: u.id, - email: u.email, - role: u.role, - })); - - res.json({ users: mappedUsers }); - } else if (req.method === "POST") { - const { email, role } = req.body; - if (!email || !role) - return res.status(400).json({ error: "Missing fields" }); - const exists = await prisma.user.findUnique({ where: { email } }); - if (exists) return res.status(409).json({ error: "Email exists" }); - const tempPassword = crypto.randomBytes(12).toString("base64").slice(0, 12); // secure random initial password - await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(tempPassword, 10), - companyId: user.companyId, - role, - }, - }); - // TODO: Email user their temp password (stub, for demo) - Implement a robust and secure email sending mechanism. Consider using a transactional email service. - res.json({ ok: true, tempPassword }); - } else res.status(405).end(); -} diff --git a/pages/api/forgot-password.ts b/pages/api/forgot-password.ts deleted file mode 100644 index fab8aca..0000000 --- a/pages/api/forgot-password.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { prisma } from "../../lib/prisma"; -import { sendEmail } from "../../lib/sendEmail"; -import crypto from "crypto"; -import type { NextApiRequest, NextApiResponse } from "next"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "POST") { - res.setHeader("Allow", ["POST"]); - return res.status(405).end(`Method ${req.method} Not Allowed`); - } - - // Type the body with a type assertion - const { email } = req.body as { email: string }; - - const user = await prisma.user.findUnique({ where: { email } }); - if (!user) return res.status(200).end(); // always 200 for privacy - - const token = crypto.randomBytes(32).toString("hex"); - const expiry = new Date(Date.now() + 1000 * 60 * 30); // 30 min expiry - await prisma.user.update({ - where: { email }, - data: { resetToken: token, resetTokenExpiry: expiry }, - }); - - const resetUrl = `${process.env.NEXTAUTH_URL || "http://localhost:3000"}/reset-password?token=${token}`; - await sendEmail(email, "Password Reset", `Reset your password: ${resetUrl}`); - res.status(200).end(); -} diff --git a/pages/api/register.ts b/pages/api/register.ts deleted file mode 100644 index f84a586..0000000 --- a/pages/api/register.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import { prisma } from "../../lib/prisma"; -import bcrypt from "bcryptjs"; -import { ApiResponse } from "../../lib/types"; - -interface RegisterRequestBody { - email: string; - password: string; - company: string; - csvUrl?: string; -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse> -) { - if (req.method !== "POST") return res.status(405).end(); - - const { email, password, company, csvUrl } = req.body as RegisterRequestBody; - - if (!email || !password || !company) { - return res.status(400).json({ - success: false, - error: "Missing required fields", - }); - } - - // Check if email exists - const exists = await prisma.user.findUnique({ - where: { email }, - }); - - if (exists) { - return res.status(409).json({ - success: false, - error: "Email already exists", - }); - } - - const newCompany = await prisma.company.create({ - data: { name: company, csvUrl: csvUrl || "" }, - }); - const hashed = await bcrypt.hash(password, 10); - await prisma.user.create({ - data: { - email, - password: hashed, - companyId: newCompany.id, - role: "admin", - }, - }); - res.status(201).json({ - success: true, - data: { success: true }, - }); -} diff --git a/pages/api/reset-password.ts b/pages/api/reset-password.ts deleted file mode 100644 index 86bd4dd..0000000 --- a/pages/api/reset-password.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { prisma } from "../../lib/prisma"; -import bcrypt from "bcryptjs"; -import type { NextApiRequest, NextApiResponse } from "next"; // Import official Next.js types - -export default async function handler( - req: NextApiRequest, // Use official NextApiRequest - res: NextApiResponse // Use official NextApiResponse -) { - if (req.method !== "POST") { - res.setHeader("Allow", ["POST"]); // Good practice to set Allow header for 405 - return res.status(405).end(`Method ${req.method} Not Allowed`); - } - - // It's good practice to explicitly type the expected body for clarity and safety - const { token, password } = req.body as { token?: string; password?: string }; - - if (!token || !password) { - return res.status(400).json({ error: "Token and password are required." }); - } - - if (password.length < 8) { - // Example: Add password complexity rule - return res - .status(400) - .json({ error: "Password must be at least 8 characters long." }); - } - - try { - const user = await prisma.user.findFirst({ - where: { - resetToken: token, - resetTokenExpiry: { gte: new Date() }, - }, - }); - - if (!user) { - return res.status(400).json({ - error: "Invalid or expired token. Please request a new password reset.", - }); - } - - const hash = await bcrypt.hash(password, 10); - await prisma.user.update({ - where: { id: user.id }, - data: { - password: hash, - resetToken: null, - resetTokenExpiry: null, - }, - }); - - // Instead of just res.status(200).end(), send a success message - return res - .status(200) - .json({ message: "Password has been reset successfully." }); - } catch (error) { - console.error("Reset password error:", error); // Log the error for server-side debugging - // Provide a generic error message to the client - return res.status(500).json({ - error: "An internal server error occurred. Please try again later.", - }); - } -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dd504f0..dca42ef 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,7 +38,7 @@ model Session { company Company @relation(fields: [companyId], references: [id]) companyId String startTime DateTime - endTime DateTime + endTime DateTime? ipAddress String? country String? language String? diff --git a/scripts/process_sessions.ts b/scripts/process_sessions.ts index 578ece0..b7e1b05 100644 --- a/scripts/process_sessions.ts +++ b/scripts/process_sessions.ts @@ -66,7 +66,7 @@ async function processTranscriptWithOpenAI( "escalated": boolean, "forwarded_hr": boolean, "category": "one of the categories listed above", - "questions": "a single question or [\"question 1\", \"question 2\", ...]", + "questions": null, or array of questions, "summary": "brief summary", "tokens": number, "tokens_eur": number