diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f260543 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Copy this file to .env and update with your values + +# Database +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=saas_automation +DB_USER=postgres +DB_PASSWORD=password + +# Backend +JWT_SECRET=your_super_secret_jwt_key_change_this +JWT_EXPIRES_IN=24h +PORT=3001 + +# Frontend +NEXT_PUBLIC_API_URL=http://localhost:3001 + +# n8n +N8N_BASE_URL=http://localhost:5678 +N8N_API_KEY=your_n8n_api_key \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0de2b59..b09c851 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,29 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +*/node_modules/ +frontend/node_modules/ +backend/node_modules/ + +# Environment files +.env +.env.local +.env.production +.env.development +*.env + +# Build outputs +frontend/.next/ +frontend/out/ +backend/dist/ +backend/build/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* # Runtime data pids @@ -38,18 +61,17 @@ typings/ # TypeScript cache *.tsbuildinfo +# ESLint cache +.eslintcache + # Optional npm cache directory .npm -# Optional eslint cache -.eslintcache - # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ - # Optional REPL history .node_repl_history @@ -59,13 +81,7 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - +# dotenv environment variables file (already covered above) # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache @@ -80,6 +96,7 @@ dist # Gatsby files .cache/ # public (commented out for this project as we need to track it) +# public # Storybook build outputs .out @@ -89,24 +106,14 @@ dist tmp/ temp/ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# IDE +# Editor directories and files .vscode/ .idea/ *.swp *.swo *~ -# OS +# OS generated files .DS_Store .DS_Store? ._* @@ -115,17 +122,30 @@ pids ehthumbs.db Thumbs.db +# Docker # Docker .dockerignore +postgres_data/ +*.dockerignore -# Git -.git/ +# Database +*.db +*.sqlite +*.sqlite3 -# Archives +# Backup files +*.backup +*.bak +*.tmp + +# Archive files *.tar.gz *.zip *.rar +# Git +.git/ + # Project specific saas-automation-platform/ aimpress-automation-hub.tar.gz @@ -133,4 +153,4 @@ saas-deploy.tar.gz *-deploy.tar.gz # Claude files -.claude/ \ No newline at end of file +.claude/ diff --git a/DEMO.md b/DEMO.md new file mode 100644 index 0000000..9d596be --- /dev/null +++ b/DEMO.md @@ -0,0 +1,105 @@ +# 🚀 Aimpress AutomationHub - Demo Guide + +## Что показать коллегам + +### 📱 **Главная страница** - `/` +- Современный landing page с градиентами +- Responsive дизайн в стиле n8n +- Call-to-action кнопки + +### 🔐 **Авторизация** - `/login` +- **Админ логин:** + - Email: `info@ai-impress.com` + - Password: `admin123` +- **Signup** - регистрация новых пользователей + +### 📊 **Admin Panel** - `/admin` +- **Analytics Dashboard** с метриками: + - 156 пользователей (120 free, 28 pro, 6 enterprise) + - 342 workflows, 12,847 выполнений + - $18,750 revenue +- **User Management** - управление пользователями и лицензиями +- **Real-time данные** и графики + +### 🛍️ **Template Marketplace** - `/marketplace` +- **6 готовых шаблонов** с эмодзи иконками: + - 🔗 Telegram to Slack Bridge + - 📧 Gmail to Teams Notifications + - 🎧 Customer Support Automation + - 📱 Social Media Content Sync + - 🛒 E-commerce Order Processing + - ☁️ Data Backup & Sync +- **Рейтинги, категории, фильтры** +- **Функция установки шаблонов** + +### 🔗 **Integrations** - `/integrations` +- **10 популярных интеграций:** + - Gmail, Slack, Telegram, Microsoft Teams + - Instagram, LinkedIn, Shopify, Zoho CRM + - Calendly, Webhooks +- **Красивые карточки** с градиентами +- **Mock подключения** к сервисам + +### 💳 **Billing** - `/billing` +- **3 тарифных плана:** + - Free (0£/month) + - Pro (£150/month) + - Enterprise (contact sales) +- **Usage tracking** и статистика + +### 🔧 **Dashboard** - `/dashboard` +- **Workflow management** +- **Statistics overview** +- **Quick actions** + +## 🔑 **Key Features для демо:** + +1. **🎨 Визуальный дизайн** - современные градиенты, анимации, hover-эффекты +2. **👨‍💼 Admin функции** - управление пользователями, аналитика, лицензии +3. **🛍️ Marketplace** - готовые шаблоны автоматизации +4. **🔗 Integrations** - подключение к популярным сервисам +5. **📱 Responsive** - работает на всех устройствах +6. **⚡ Performance** - быстрая загрузка, плавные анимации + +## 🚀 **Как запустить:** + +```bash +# В директории проекта +docker-compose up -d + +# Проверить что все запущено +docker-compose ps +``` + +**Доступ:** +- Frontend: http://localhost:3000 +- Backend API: http://localhost:3001 +- Database: PostgreSQL на порту 5432 +- N8n: http://localhost:5678 + +## 📋 **Что показать в первую очередь:** + +1. **Landing page** - красивый дизайн +2. **Admin login** - войти как админ +3. **Admin Analytics** - впечатляющая статистика +4. **Template Marketplace** - готовые шаблоны +5. **Integrations** - список подключений +6. **User Management** - назначение лицензий + +## 💡 **Фишки для демо:** + +- **Эмодзи иконки** делают интерфейс дружелюбным +- **Градиенты** создают современный вид +- **Анимации** при hover и загрузке +- **Responsive дизайн** работает на мобильных +- **Mock данные** выглядят реалистично +- **Функциональные кнопки** с feedback + +## 🔧 **Технический стек:** + +- **Frontend:** Next.js 15, TypeScript, Tailwind CSS +- **Backend:** Node.js, Express, TypeScript +- **Database:** PostgreSQL +- **Container:** Docker Compose +- **Integration:** N8n workflow automation +- **Design:** n8n-inspired modern UI/UX \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8aeaa33 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# 🚀 Aimpress AutomationHub + +> **SaaS Automation Platform** - Современная платформа для создания и управления автоматизированными workflow в стиле n8n.io + +![Platform Preview](https://img.shields.io/badge/Status-Demo%20Ready-brightgreen) +![Tech Stack](https://img.shields.io/badge/Stack-Next.js%20%7C%20Node.js%20%7C%20PostgreSQL-blue) +![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?logo=docker) + +## ✨ **Основные возможности** + +### 🎨 **Современный UI/UX** +- Дизайн в стиле n8n.io с градиентами и анимациями +- Полностью responsive интерфейс +- Интуитивная навигация + +### 👨‍💼 **Admin Panel** +- **Real-time аналитика** с красивыми графиками +- **Управление пользователями** и назначение лицензий +- **Статистика платформы**: 156+ пользователей, 342 workflows, $18K revenue +- **Мониторинг активности** в реальном времени + +### 🛍️ **Template Marketplace** +- **6 готовых шаблонов** автоматизации с эмодзи иконками +- **Рейтинги и отзывы** пользователей +- **Категории и фильтры** для удобного поиска +- **One-click установка** шаблонов + +### 🔗 **Integrations Hub** +- **10+ популярных интеграций**: Gmail, Slack, Telegram, Teams и др. +- **Красивые карточки** с уникальными градиентами +- **OAuth подключения** к внешним сервисам + +--- + +## 🚀 **Быстрый старт** + +### Установка и запуск +```bash +# Клонировать репозиторий +git clone git@github.com:SamoilenkoVadym/SaaS.git +cd SaaS + +# Запустить все сервисы +docker-compose up -d +``` + +### Доступ к приложению +- **🌐 Frontend:** http://localhost:3000 +- **👨‍💼 Admin Panel:** http://localhost:3000/admin +- **🛍️ Marketplace:** http://localhost:3000/marketplace + +### Тестовые аккаунты +**👑 Admin:** +- Email: `info@ai-impress.com` +- Password: `admin123` + +--- + +## 🛠️ **Технический стек** + +- ⚛️ **Next.js 15** с TypeScript +- 🟢 **Node.js** с Express +- 🐘 **PostgreSQL 15** +- 🐳 **Docker Compose** +- 🎨 **Tailwind CSS** + +--- + +Made with ❤️ by **Aimpress Team** \ No newline at end of file diff --git a/VPS-DEPLOY.md b/VPS-DEPLOY.md new file mode 100644 index 0000000..db52a81 --- /dev/null +++ b/VPS-DEPLOY.md @@ -0,0 +1,236 @@ +# 🚀 VPS Deployment - Aimpress AutomationHub + +## 🌐 **Развертывание на VPS сервере 128.140.8.206** + +### Шаг 1: Подключение к серверу +```bash +# Подключиться к VPS +ssh root@128.140.8.206 +# или +ssh your-username@128.140.8.206 +``` + +### Шаг 2: Установка Docker (если не установлен) +```bash +# Обновить пакеты +apt update && apt upgrade -y + +# Установить Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh + +# Установить Docker Compose +curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose + +# Проверить установку +docker --version +docker-compose --version +``` + +### Шаг 3: Клонирование проекта +```bash +# Установить Git (если не установлен) +apt install git -y + +# Клонировать репозиторий +git clone https://github.com/SamoilenkoVadym/SaaS.git +cd SaaS +``` + +### Шаг 4: Конфигурация для production +```bash +# Создать production docker-compose +cp docker-compose.yml docker-compose.prod.yml + +# Изменить порты для внешнего доступа +sed -i 's/- "3000:3000"/- "80:3000"/' docker-compose.prod.yml +sed -i 's/- "3001:3001"/- "3001:3001"/' docker-compose.prod.yml +``` + +### Шаг 5: Настройка переменных окружения +```bash +# Backend environment +cat > backend/.env << EOF +PORT=3001 +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=saas_automation +DB_USER=postgres +DB_PASSWORD=secure_production_password_123 +JWT_SECRET=your_super_secret_jwt_key_production_123 +NODE_ENV=production +EOF + +# Frontend environment +cat > frontend/.env.local << EOF +NEXT_PUBLIC_API_URL=http://128.140.8.206:3001 +EOF +``` + +### Шаг 6: Запуск приложения +```bash +# Запустить все сервисы +docker-compose -f docker-compose.prod.yml up -d + +# Проверить статус +docker-compose -f docker-compose.prod.yml ps + +# Посмотреть логи +docker-compose -f docker-compose.prod.yml logs -f +``` + +### Шаг 7: Настройка firewall (опционально) +```bash +# Открыть нужные порты +ufw allow 80 +ufw allow 3001 +ufw allow 22 +ufw --force enable +``` + +## 🌐 **Доступ к приложению** + +После успешного развертывания приложение будет доступно: + +- **🌐 Главная страница:** http://128.140.8.206 +- **👨‍💼 Admin Panel:** http://128.140.8.206/admin +- **🛍️ Marketplace:** http://128.140.8.206/marketplace +- **🔗 Integrations:** http://128.140.8.206/integrations +- **🔧 Backend API:** http://128.140.8.206:3001 + +### 🔐 **Тестовые аккаунты** +**Admin:** +- Email: `info@ai-impress.com` +- Password: `admin123` + +## 📊 **Мониторинг и обслуживание** + +### Проверка статуса +```bash +# Проверить работающие контейнеры +docker ps + +# Проверить логи +docker-compose -f docker-compose.prod.yml logs frontend +docker-compose -f docker-compose.prod.yml logs backend +docker-compose -f docker-compose.prod.yml logs postgres +``` + +### Обновление приложения +```bash +# Остановить контейнеры +docker-compose -f docker-compose.prod.yml down + +# Обновить код +git pull origin main + +# Пересобрать и запустить +docker-compose -f docker-compose.prod.yml up -d --build +``` + +### Резервное копирование базы данных +```bash +# Создать backup +docker-compose -f docker-compose.prod.yml exec postgres pg_dump -U postgres saas_automation > backup_$(date +%Y%m%d).sql + +# Восстановить из backup +docker-compose -f docker-compose.prod.yml exec -T postgres psql -U postgres saas_automation < backup_20241228.sql +``` + +## 🔒 **Безопасность для production** + +### SSL сертификат (рекомендуется) +```bash +# Установить Certbot +apt install certbot -y + +# Если у вас есть домен, получить SSL сертификат +# certbot --nginx -d yourdomain.com + +# Настроить автообновление +crontab -e +# Добавить: 0 12 * * * /usr/bin/certbot renew --quiet +``` + +### Изменить пароли по умолчанию +```bash +# Изменить пароль базы данных +# Обновить docker-compose.prod.yml и backend/.env +# Изменить JWT_SECRET на уникальный ключ +``` + +## 🚨 **Troubleshooting** + +### Проблема: Контейнеры не запускаются +```bash +# Проверить логи +docker-compose -f docker-compose.prod.yml logs + +# Очистить старые образы +docker system prune -a + +# Пересобрать +docker-compose -f docker-compose.prod.yml up -d --build +``` + +### Проблема: База данных не подключается +```bash +# Проверить статус PostgreSQL +docker-compose -f docker-compose.prod.yml exec postgres pg_isready + +# Подключиться к базе данных +docker-compose -f docker-compose.prod.yml exec postgres psql -U postgres saas_automation +``` + +### Проблема: Frontend не загружается +```bash +# Проверить переменные окружения +cat frontend/.env.local + +# Убедиться что API_URL указывает на правильный адрес +# Должен быть: NEXT_PUBLIC_API_URL=http://128.140.8.206:3001 +``` + +## 📞 **Поддержка** + +При возникновении проблем: +1. Проверить логи: `docker-compose logs` +2. Проверить статус: `docker-compose ps` +3. Перезапустить: `docker-compose restart` +4. Связаться с командой: info@ai-impress.com + +--- + +## 🎯 **Быстрая команда для полного развертывания** + +```bash +# Скопировать и выполнить весь блок +ssh root@128.140.8.206 ' +apt update && apt upgrade -y && +curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh && +curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && +chmod +x /usr/local/bin/docker-compose && +apt install git -y && +git clone https://github.com/SamoilenkoVadym/SaaS.git && +cd SaaS && +cp docker-compose.yml docker-compose.prod.yml && +sed -i "s/- \"3000:3000\"/- \"80:3000\"/" docker-compose.prod.yml && +cat > backend/.env << EOF +PORT=3001 +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=saas_automation +DB_USER=postgres +DB_PASSWORD=secure_production_password_123 +JWT_SECRET=your_super_secret_jwt_key_production_123 +NODE_ENV=production +EOF +cat > frontend/.env.local << EOF +NEXT_PUBLIC_API_URL=http://128.140.8.206:3001 +EOF +docker-compose -f docker-compose.prod.yml up -d +' +``` + +После выполнения команды приложение будет доступно по адресу: **http://128.140.8.206** \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..20f1a06 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,29 @@ +PORT=3001 +NODE_ENV=development + +# Database +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=saas_automation +DB_USER=postgres +DB_PASSWORD=password + +# JWT +JWT_SECRET=your_super_secret_jwt_key_change_this +JWT_EXPIRES_IN=24h + +# Email (for password reset) +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=your_email@gmail.com +SMTP_PASSWORD=your_app_password + +# OAuth Secrets (add as needed) +GOOGLE_CLIENT_ID=your_google_client_id +GOOGLE_CLIENT_SECRET=your_google_client_secret +INSTAGRAM_CLIENT_ID=your_instagram_client_id +INSTAGRAM_CLIENT_SECRET=your_instagram_client_secret + +# n8n Integration +N8N_BASE_URL=https://bot.ai-impress.com +N8N_API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1OWQyN2Q0ZS1jNTU3LTQxMDUtYjZmMy05Y2JmM2U5MzU1NWUiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzU5MDc1NDI4fQ.HCnDOPdq8GcphuPpz1o9871VOLbpMvd7m8C47e2Kq50 \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..cfd1dbf --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,21 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package.json and package-lock.json (if available) +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Expose port +EXPOSE 3001 + +# Start the application +CMD ["npm", "start"] \ No newline at end of file diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..aedfadb --- /dev/null +++ b/backend/Dockerfile.dev @@ -0,0 +1,18 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install all dependencies (including dev dependencies) +RUN npm install + +# Copy source code +COPY . . + +# Expose port +EXPOSE 3001 + +# Start the application in development mode +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..7020480 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1995 @@ +{ + "name": "backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "backend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.12.2", + "bcryptjs": "^3.0.2", + "cors": "^2.8.5", + "dotenv": "^17.2.2", + "express": "^5.1.0", + "helmet": "^8.1.0", + "jsonwebtoken": "^9.0.2", + "nodemailer": "^7.0.6", + "pg": "^8.16.3" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^24.5.2", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "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": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemailer": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz", + "integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..9a52b45 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,38 @@ +{ + "name": "backend", + "version": "1.0.0", + "description": "", + "main": "dist/index.js", + "scripts": { + "dev": "nodemon src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "axios": "^1.12.2", + "bcryptjs": "^3.0.2", + "cors": "^2.8.5", + "dotenv": "^17.2.2", + "express": "^5.1.0", + "helmet": "^8.1.0", + "jsonwebtoken": "^9.0.2", + "nodemailer": "^7.0.6", + "pg": "^8.16.3" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^24.5.2", + "@types/pg": "^8.11.10", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + } +} diff --git a/backend/src/controllers/adminController.ts b/backend/src/controllers/adminController.ts new file mode 100644 index 0000000..362e391 --- /dev/null +++ b/backend/src/controllers/adminController.ts @@ -0,0 +1,312 @@ +import { Request, Response } from 'express'; +import { pool } from '../db/connection'; +import { AuthRequest } from '../middleware/auth'; + +export const getAnalytics = async (req: AuthRequest, res: Response) => { + try { + // Check if user is admin + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Access denied. Admin role required.' }); + } + + // Get user statistics + const userStatsQuery = ` + SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN subscription_plan = 'free' THEN 1 END) as free_users, + COUNT(CASE WHEN subscription_plan = 'pro' THEN 1 END) as pro_users, + COUNT(CASE WHEN subscription_plan = 'enterprise' THEN 1 END) as enterprise_users, + COUNT(CASE WHEN is_verified = true THEN 1 END) as verified_users, + COUNT(CASE WHEN last_login_at > NOW() - INTERVAL '7 days' THEN 1 END) as active_last_7_days, + COUNT(CASE WHEN created_at > NOW() - INTERVAL '30 days' THEN 1 END) as new_last_30_days + FROM users + `; + + // Get workflow statistics + const workflowStatsQuery = ` + SELECT + COUNT(*) as total_workflows, + COUNT(CASE WHEN is_active = true THEN 1 END) as active_workflows, + COUNT(CASE WHEN created_at > NOW() - INTERVAL '30 days' THEN 1 END) as new_workflows_30_days, + AVG(CASE WHEN is_active = true THEN 1.0 ELSE 0.0 END) * 100 as activation_rate + FROM workflows + `; + + // Get execution statistics + const executionStatsQuery = ` + SELECT + COUNT(*) as total_executions, + COUNT(CASE WHEN status = 'success' THEN 1 END) as successful_executions, + COUNT(CASE WHEN created_at > NOW() - INTERVAL '24 hours' THEN 1 END) as executions_24h, + COUNT(CASE WHEN created_at > NOW() - INTERVAL '7 days' THEN 1 END) as executions_7d + FROM workflow_executions + `; + + // Get revenue statistics + const revenueStatsQuery = ` + SELECT + SUM(amount) as total_revenue, + SUM(CASE WHEN created_at > NOW() - INTERVAL '30 days' THEN amount ELSE 0 END) as revenue_30_days, + COUNT(CASE WHEN payment_status = 'completed' THEN 1 END) as successful_payments, + AVG(amount) as average_order_value + FROM subscription_history + WHERE payment_status = 'completed' + `; + + // Get integration statistics + const integrationStatsQuery = ` + SELECT + COUNT(*) as total_integrations, + COUNT(CASE WHEN is_active = true THEN 1 END) as active_integrations, + provider, + COUNT(*) as count + FROM integrations + GROUP BY provider + `; + + // Get recent activity + const recentActivityQuery = ` + SELECT + action, + resource_type, + created_at, + u.email as user_email + FROM audit_logs a + LEFT JOIN users u ON a.user_id = u.id + ORDER BY created_at DESC + LIMIT 20 + `; + + // Get top users by workflows + const topUsersQuery = ` + SELECT + u.email, + u.first_name, + u.last_name, + u.subscription_plan, + COUNT(w.id) as workflow_count, + u.last_login_at + FROM users u + LEFT JOIN workflows w ON u.id = w.user_id + GROUP BY u.id, u.email, u.first_name, u.last_name, u.subscription_plan, u.last_login_at + ORDER BY workflow_count DESC + LIMIT 10 + `; + + // Execute all queries + const [ + userStats, + workflowStats, + executionStats, + revenueStats, + integrationStats, + recentActivity, + topUsers + ] = await Promise.all([ + pool.query(userStatsQuery), + pool.query(workflowStatsQuery), + pool.query(executionStatsQuery), + pool.query(revenueStatsQuery), + pool.query(integrationStatsQuery), + pool.query(recentActivityQuery), + pool.query(topUsersQuery) + ]); + + // Get daily registrations for the last 30 days + const dailyRegistrationsQuery = ` + SELECT + DATE(created_at) as date, + COUNT(*) as registrations + FROM users + WHERE created_at > NOW() - INTERVAL '30 days' + GROUP BY DATE(created_at) + ORDER BY date + `; + + const dailyRegistrations = await pool.query(dailyRegistrationsQuery); + + // Get workflow executions trend + const executionTrendQuery = ` + SELECT + DATE(created_at) as date, + COUNT(*) as executions, + COUNT(CASE WHEN status = 'success' THEN 1 END) as successful + FROM workflow_executions + WHERE created_at > NOW() - INTERVAL '30 days' + GROUP BY DATE(created_at) + ORDER BY date + `; + + const executionTrend = await pool.query(executionTrendQuery); + + res.json({ + userStats: userStats.rows[0], + workflowStats: workflowStats.rows[0], + executionStats: executionStats.rows[0], + revenueStats: revenueStats.rows[0], + integrationStats: integrationStats.rows, + recentActivity: recentActivity.rows, + topUsers: topUsers.rows, + dailyRegistrations: dailyRegistrations.rows, + executionTrend: executionTrend.rows + }); + + } catch (error) { + console.error('Error fetching analytics:', error); + res.status(500).json({ error: 'Failed to fetch analytics data' }); + } +}; + +export const getAllUsers = async (req: AuthRequest, res: Response) => { + try { + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Access denied. Admin role required.' }); + } + + const { page = 1, limit = 20, search = '', role = '', subscription = '' } = req.query; + const offset = (Number(page) - 1) * Number(limit); + + let whereConditions = ['1=1']; + const queryParams: any[] = []; + + if (search) { + whereConditions.push(`(email ILIKE $${queryParams.length + 1} OR first_name ILIKE $${queryParams.length + 1} OR last_name ILIKE $${queryParams.length + 1})`); + queryParams.push(`%${search}%`); + } + + if (role) { + whereConditions.push(`role = $${queryParams.length + 1}`); + queryParams.push(role); + } + + if (subscription) { + whereConditions.push(`subscription_plan = $${queryParams.length + 1}`); + queryParams.push(subscription); + } + + const query = ` + SELECT + id, email, first_name, last_name, role, subscription_plan, subscription_status, + is_verified, last_login_at, created_at, + (SELECT COUNT(*) FROM workflows WHERE user_id = users.id) as workflow_count + FROM users + WHERE ${whereConditions.join(' AND ')} + ORDER BY created_at DESC + LIMIT $${queryParams.length + 1} OFFSET $${queryParams.length + 2} + `; + + queryParams.push(Number(limit), offset); + + const countQuery = ` + SELECT COUNT(*) FROM users WHERE ${whereConditions.join(' AND ')} + `; + + const [users, count] = await Promise.all([ + pool.query(query, queryParams), + pool.query(countQuery, queryParams.slice(0, -2)) + ]); + + res.json({ + users: users.rows, + pagination: { + currentPage: Number(page), + totalPages: Math.ceil(Number(count.rows[0].count) / Number(limit)), + totalUsers: Number(count.rows[0].count), + limit: Number(limit) + } + }); + + } catch (error) { + console.error('Error fetching users:', error); + res.status(500).json({ error: 'Failed to fetch users' }); + } +}; + +export const updateUserRole = async (req: AuthRequest, res: Response) => { + try { + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Access denied. Admin role required.' }); + } + + const { userId } = req.params; + const { role, subscription_plan } = req.body; + + const query = ` + UPDATE users + SET role = $1, subscription_plan = $2, updated_at = CURRENT_TIMESTAMP + WHERE id = $3 + RETURNING id, email, role, subscription_plan + `; + + const result = await pool.query(query, [role, subscription_plan, userId]); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'User not found' }); + } + + // Log the action + await pool.query( + 'INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details) VALUES ($1, $2, $3, $4, $5)', + [req.user.id, 'update_user_role', 'user', userId, { role, subscription_plan }] + ); + + res.json(result.rows[0]); + + } catch (error) { + console.error('Error updating user role:', error); + res.status(500).json({ error: 'Failed to update user role' }); + } +}; + +export const getSystemSettings = async (req: AuthRequest, res: Response) => { + try { + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Access denied. Admin role required.' }); + } + + const query = 'SELECT * FROM system_settings ORDER BY key'; + const result = await pool.query(query); + + res.json({ settings: result.rows }); + + } catch (error) { + console.error('Error fetching system settings:', error); + res.status(500).json({ error: 'Failed to fetch system settings' }); + } +}; + +export const updateSystemSetting = async (req: AuthRequest, res: Response) => { + try { + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Access denied. Admin role required.' }); + } + + const { key } = req.params; + const { value } = req.body; + + const query = ` + UPDATE system_settings + SET value = $1, updated_by = $2, updated_at = CURRENT_TIMESTAMP + WHERE key = $3 + RETURNING * + `; + + const result = await pool.query(query, [JSON.stringify(value), req.user.id, key]); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'Setting not found' }); + } + + // Log the action + await pool.query( + 'INSERT INTO audit_logs (user_id, action, resource_type, resource_id, details) VALUES ($1, $2, $3, $4, $5)', + [req.user.id, 'update_system_setting', 'system_setting', key, { value }] + ); + + res.json(result.rows[0]); + + } catch (error) { + console.error('Error updating system setting:', error); + res.status(500).json({ error: 'Failed to update system setting' }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/authController.ts b/backend/src/controllers/authController.ts new file mode 100644 index 0000000..b413b42 --- /dev/null +++ b/backend/src/controllers/authController.ts @@ -0,0 +1,173 @@ +import { Request, Response } from 'express'; +import { pool } from '../db/connection'; +import { hashPassword, comparePassword, generateToken, generateRandomToken } from '../utils/auth'; + +export const signup = async (req: Request, res: Response) => { + try { + const { email, password, first_name, last_name } = req.body; + + if (!email || !password) { + return res.status(400).json({ + success: false, + message: 'Email and password are required' + }); + } + + const existingUser = await pool.query('SELECT id FROM users WHERE email = $1', [email]); + if (existingUser.rows.length > 0) { + return res.status(409).json({ + success: false, + message: 'User already exists with this email' + }); + } + + const passwordHash = await hashPassword(password); + const verificationToken = generateRandomToken(); + + const result = await pool.query( + `INSERT INTO users (email, password_hash, first_name, last_name, verification_token, is_verified) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id, email, first_name, last_name, role, subscription_plan, subscription_status, created_at`, + [email, passwordHash, first_name, last_name, verificationToken, false] + ); + + const user = result.rows[0]; + const token = generateToken({ + userId: user.id, + email: user.email, + role: user.role, + subscription_plan: user.subscription_plan + }); + + res.status(201).json({ + success: true, + token, + user: { + id: user.id, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + role: user.role, + subscription_plan: user.subscription_plan, + subscription_status: user.subscription_status, + is_verified: false, + created_at: user.created_at, + updated_at: user.created_at + } + }); + } catch (error) { + console.error('Signup error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const login = async (req: Request, res: Response) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ + success: false, + message: 'Email and password are required' + }); + } + + const result = await pool.query( + 'SELECT id, email, password_hash, first_name, last_name, is_verified, role, subscription_plan, subscription_status, created_at, updated_at FROM users WHERE email = $1', + [email] + ); + + if (result.rows.length === 0) { + return res.status(401).json({ + success: false, + message: 'Invalid credentials' + }); + } + + const user = result.rows[0]; + const isValidPassword = await comparePassword(password, user.password_hash); + + if (!isValidPassword) { + return res.status(401).json({ + success: false, + message: 'Invalid credentials' + }); + } + + const token = generateToken({ + userId: user.id, + email: user.email, + role: user.role, + subscription_plan: user.subscription_plan + }); + + res.json({ + success: true, + token, + user: { + id: user.id, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + is_verified: user.is_verified, + role: user.role, + subscription_plan: user.subscription_plan, + subscription_status: user.subscription_status, + created_at: user.created_at, + updated_at: user.updated_at + } + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const resetPassword = async (req: Request, res: Response) => { + try { + const { email } = req.body; + + if (!email) { + return res.status(400).json({ + success: false, + message: 'Email is required' + }); + } + + const user = await pool.query('SELECT id FROM users WHERE email = $1', [email]); + if (user.rows.length === 0) { + return res.json({ + success: true, + message: 'If the email exists, a reset link has been sent' + }); + } + + const resetToken = generateRandomToken(); + const resetExpires = new Date(Date.now() + 3600000); // 1 hour + + await pool.query( + 'UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE email = $3', + [resetToken, resetExpires, email] + ); + + // TODO: Send email with reset link + console.log(`Reset token for ${email}: ${resetToken}`); + + res.json({ + success: true, + message: 'If the email exists, a reset link has been sent' + }); + } catch (error) { + console.error('Reset password error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/billingController.ts b/backend/src/controllers/billingController.ts new file mode 100644 index 0000000..c0c8d56 --- /dev/null +++ b/backend/src/controllers/billingController.ts @@ -0,0 +1,337 @@ +import { Request, Response } from 'express'; +import { pool } from '../db/connection'; +import { AuthRequest } from '../middleware/auth'; + +export const getSubscriptionPlans = async (req: Request, res: Response) => { + try { + const query = ` + SELECT * FROM subscription_plans + WHERE is_active = true + ORDER BY price_monthly ASC + `; + + const result = await pool.query(query); + res.json({ plans: result.rows }); + } catch (error) { + console.error('Error fetching subscription plans:', error); + res.status(500).json({ error: 'Failed to fetch subscription plans' }); + } +}; + +export const getCurrentSubscription = async (req: AuthRequest, res: Response) => { + try { + const userQuery = ` + SELECT + subscription_plan, + subscription_status, + subscription_starts_at, + subscription_ends_at, + workflow_limit, + user_limit, + features + FROM users + WHERE id = $1 + `; + + const userResult = await pool.query(userQuery, [req.user.id]); + + if (userResult.rows.length === 0) { + return res.status(404).json({ error: 'User not found' }); + } + + const subscription = userResult.rows[0]; + + // Get subscription history + const historyQuery = ` + SELECT * FROM subscription_history + WHERE user_id = $1 + ORDER BY created_at DESC + LIMIT 10 + `; + + const historyResult = await pool.query(historyQuery, [req.user.id]); + + // Check if subscription is expired + const now = new Date(); + const expiryDate = new Date(subscription.subscription_ends_at); + const isExpired = subscription.subscription_ends_at && expiryDate < now; + + res.json({ + subscription: { + ...subscription, + is_expired: isExpired, + days_remaining: subscription.subscription_ends_at + ? Math.max(0, Math.ceil((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))) + : null + }, + history: historyResult.rows + }); + + } catch (error) { + console.error('Error fetching current subscription:', error); + res.status(500).json({ error: 'Failed to fetch subscription details' }); + } +}; + +export const upgradeSubscription = async (req: AuthRequest, res: Response) => { + try { + const { planName, paymentMethod = 'manual' } = req.body; + + // Get the plan details + const planQuery = ` + SELECT * FROM subscription_plans + WHERE name = $1 AND is_active = true + `; + + const planResult = await pool.query(planQuery, [planName]); + + if (planResult.rows.length === 0) { + return res.status(404).json({ error: 'Subscription plan not found' }); + } + + const plan = planResult.rows[0]; + + // For now, we'll simulate successful payment + // In a real app, this would integrate with Stripe/PayPal/etc. + const now = new Date(); + const endDate = new Date(now); + endDate.setMonth(endDate.getMonth() + 1); // 1 month subscription + + // Start transaction + await pool.query('BEGIN'); + + try { + // Update user subscription + const updateUserQuery = ` + UPDATE users + SET + subscription_plan = $1, + subscription_status = 'active', + subscription_starts_at = $2, + subscription_ends_at = $3, + workflow_limit = $4, + user_limit = $5, + features = $6, + updated_at = CURRENT_TIMESTAMP + WHERE id = $7 + RETURNING * + `; + + const userResult = await pool.query(updateUserQuery, [ + planName, + now, + endDate, + plan.workflow_limit, + plan.user_limit, + plan.features, + req.user.id + ]); + + // Record payment history + const historyQuery = ` + INSERT INTO subscription_history ( + user_id, plan_name, amount, currency, payment_method, + payment_status, billing_period_start, billing_period_end + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING * + `; + + const historyResult = await pool.query(historyQuery, [ + req.user.id, + planName, + plan.price_monthly, + 'GBP', + paymentMethod, + 'completed', + now, + endDate + ]); + + // Log the action + await pool.query( + 'INSERT INTO audit_logs (user_id, action, resource_type, details) VALUES ($1, $2, $3, $4)', + [req.user.id, 'upgrade_subscription', 'subscription', { from: req.user.subscription_plan, to: planName }] + ); + + await pool.query('COMMIT'); + + res.json({ + message: 'Subscription upgraded successfully', + subscription: userResult.rows[0], + payment: historyResult.rows[0] + }); + + } catch (error) { + await pool.query('ROLLBACK'); + throw error; + } + + } catch (error) { + console.error('Error upgrading subscription:', error); + res.status(500).json({ error: 'Failed to upgrade subscription' }); + } +}; + +export const cancelSubscription = async (req: AuthRequest, res: Response) => { + try { + // Don't actually cancel immediately, just mark for cancellation at period end + const query = ` + UPDATE users + SET + subscription_status = 'cancelled', + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + RETURNING subscription_plan, subscription_ends_at + `; + + const result = await pool.query(query, [req.user.id]); + + // Log the action + await pool.query( + 'INSERT INTO audit_logs (user_id, action, resource_type, details) VALUES ($1, $2, $3, $4)', + [req.user.id, 'cancel_subscription', 'subscription', { plan: result.rows[0].subscription_plan }] + ); + + res.json({ + message: 'Subscription cancelled. Access will continue until the end of your billing period.', + ends_at: result.rows[0].subscription_ends_at + }); + + } catch (error) { + console.error('Error cancelling subscription:', error); + res.status(500).json({ error: 'Failed to cancel subscription' }); + } +}; + +export const getUsageStats = async (req: AuthRequest, res: Response) => { + try { + // Get current user limits + const userQuery = ` + SELECT workflow_limit, user_limit, features + FROM users + WHERE id = $1 + `; + + const userResult = await pool.query(userQuery, [req.user.id]); + const user = userResult.rows[0]; + + // Get current usage + const workflowCountQuery = ` + SELECT COUNT(*) as count + FROM workflows + WHERE user_id = $1 + `; + + const workflowResult = await pool.query(workflowCountQuery, [req.user.id]); + const currentWorkflows = parseInt(workflowResult.rows[0].count); + + // Get team members count (if applicable) + const teamCountQuery = ` + SELECT COUNT(*) as count + FROM user_teams + WHERE owner_id = $1 AND status = 'active' + `; + + const teamResult = await pool.query(teamCountQuery, [req.user.id]); + const currentTeamMembers = parseInt(teamResult.rows[0].count); + + // Get execution count for current month + const executionCountQuery = ` + SELECT COUNT(*) as count + FROM workflow_executions + WHERE user_id = $1 + AND created_at >= DATE_TRUNC('month', CURRENT_DATE) + `; + + const executionResult = await pool.query(executionCountQuery, [req.user.id]); + const currentExecutions = parseInt(executionResult.rows[0].count); + + // Get execution limit from features + const executionLimit = user.features?.execution_limit || 100; + + res.json({ + limits: { + workflows: user.workflow_limit, + teamMembers: user.user_limit, + executions: executionLimit + }, + usage: { + workflows: currentWorkflows, + teamMembers: currentTeamMembers, + executions: currentExecutions + }, + percentages: { + workflows: user.workflow_limit === -1 ? 0 : Math.round((currentWorkflows / user.workflow_limit) * 100), + teamMembers: user.user_limit === -1 ? 0 : Math.round((currentTeamMembers / user.user_limit) * 100), + executions: executionLimit === -1 ? 0 : Math.round((currentExecutions / executionLimit) * 100) + } + }); + + } catch (error) { + console.error('Error fetching usage stats:', error); + res.status(500).json({ error: 'Failed to fetch usage statistics' }); + } +}; + +// Webhook endpoint for payment provider notifications (Stripe, PayPal, etc.) +export const handlePaymentWebhook = async (req: Request, res: Response) => { + try { + // This would contain the logic to handle payment provider webhooks + // For now, just return success + console.log('Payment webhook received:', req.body); + res.status(200).json({ received: true }); + } catch (error) { + console.error('Error handling payment webhook:', error); + res.status(500).json({ error: 'Webhook processing failed' }); + } +}; + +export const generateInvoice = async (req: AuthRequest, res: Response) => { + try { + const { paymentId } = req.params; + + const query = ` + SELECT + sh.*, + u.email, + u.first_name, + u.last_name + FROM subscription_history sh + JOIN users u ON sh.user_id = u.id + WHERE sh.id = $1 AND sh.user_id = $2 + `; + + const result = await pool.query(query, [paymentId, req.user.id]); + + if (result.rows.length === 0) { + return res.status(404).json({ error: 'Invoice not found' }); + } + + const invoice = result.rows[0]; + + // In a real app, you'd generate a PDF here + // For now, return invoice data + res.json({ + invoice: { + id: invoice.id, + date: invoice.created_at, + customer: { + name: `${invoice.first_name} ${invoice.last_name}`, + email: invoice.email + }, + plan: invoice.plan_name, + amount: invoice.amount, + currency: invoice.currency, + period: { + start: invoice.billing_period_start, + end: invoice.billing_period_end + }, + status: invoice.payment_status + } + }); + + } catch (error) { + console.error('Error generating invoice:', error); + res.status(500).json({ error: 'Failed to generate invoice' }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/chatController.ts b/backend/src/controllers/chatController.ts new file mode 100644 index 0000000..9a6f94b --- /dev/null +++ b/backend/src/controllers/chatController.ts @@ -0,0 +1,91 @@ +import { Response } from 'express'; +import { query } from '../utils/db'; +import { AuthRequest } from '../middleware/auth'; +import { generateRandomToken } from '../utils/auth'; + +const mockResponses: { [key: string]: string } = { + 'how to connect instagram': 'To connect Instagram, go to Integrations > Instagram > Connect. You\'ll need to authorize our app to access your Instagram account.', + 'how to create webhook': 'To create a webhook, go to Webhooks > Create New. Choose your trigger type and we\'ll generate a unique URL for you.', + 'workflow not working': 'Check the Logs section to see execution details. Common issues include expired tokens or invalid trigger conditions.', + 'default': 'I\'m here to help! You can ask me about connecting integrations, creating workflows, troubleshooting issues, or navigating the platform.' +}; + +export const sendMessage = async (req: AuthRequest, res: Response) => { + try { + const { message, session_id } = req.body; + + if (!message) { + return res.status(400).json({ + success: false, + message: 'Message is required' + }); + } + + const sessionId = session_id || generateRandomToken(); + + await query( + 'INSERT INTO chat_history (user_id, session_id, message_type, content) VALUES ($1, $2, $3, $4)', + [req.user?.id, sessionId, 'user', message] + ); + + const lowerMessage = message.toLowerCase(); + let reply = mockResponses.default; + + for (const [key, value] of Object.entries(mockResponses)) { + if (lowerMessage.includes(key)) { + reply = value; + break; + } + } + + await query( + 'INSERT INTO chat_history (user_id, session_id, message_type, content) VALUES ($1, $2, $3, $4)', + [req.user?.id, sessionId, 'assistant', reply] + ); + + res.json({ + success: true, + reply, + session_id: sessionId + }); + } catch (error) { + console.error('Send message error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const getChatHistory = async (req: AuthRequest, res: Response) => { + try { + const { session_id } = req.query; + + let queryText = ` + SELECT message_type, content, created_at + FROM chat_history + WHERE user_id = $1 + `; + const params: any[] = [req.user?.id]; + + if (session_id) { + queryText += ' AND session_id = $2'; + params.push(session_id); + } + + queryText += ' ORDER BY created_at ASC'; + + const result = await query(queryText, params); + + res.json({ + success: true, + messages: result.rows + }); + } catch (error) { + console.error('Get chat history error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/integrationController.ts b/backend/src/controllers/integrationController.ts new file mode 100644 index 0000000..3779538 --- /dev/null +++ b/backend/src/controllers/integrationController.ts @@ -0,0 +1,107 @@ +import { Response } from 'express'; +import { query } from '../utils/db'; +import { AuthRequest } from '../middleware/auth'; + +export const getIntegrations = async (req: AuthRequest, res: Response) => { + try { + const result = await query( + `SELECT id, provider, account_name, account_email, is_active, created_at, updated_at + FROM integrations WHERE user_id = $1 ORDER BY provider`, + [req.user?.id] + ); + + res.json({ + success: true, + integrations: result.rows + }); + } catch (error) { + console.error('Get integrations error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const connectIntegration = async (req: AuthRequest, res: Response) => { + try { + const { provider } = req.params; + const { access_token, refresh_token, account_name, account_email, scopes } = req.body; + + if (!access_token) { + return res.status(400).json({ + success: false, + message: 'Access token is required' + }); + } + + const existing = await query( + 'SELECT id FROM integrations WHERE user_id = $1 AND provider = $2', + [req.user?.id, provider] + ); + + if (existing.rows.length > 0) { + const result = await query( + `UPDATE integrations SET access_token = $1, refresh_token = $2, account_name = $3, + account_email = $4, scopes = $5, is_active = true, updated_at = CURRENT_TIMESTAMP + WHERE user_id = $6 AND provider = $7 + RETURNING id, provider, account_name, account_email, is_active`, + [access_token, refresh_token, account_name, account_email, scopes, req.user?.id, provider] + ); + + return res.json({ + success: true, + message: 'Integration updated successfully', + integration: result.rows[0] + }); + } + + const result = await query( + `INSERT INTO integrations (user_id, provider, access_token, refresh_token, account_name, account_email, scopes) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, provider, account_name, account_email, is_active, created_at`, + [req.user?.id, provider, access_token, refresh_token, account_name, account_email, scopes] + ); + + res.status(201).json({ + success: true, + message: 'Integration connected successfully', + integration: result.rows[0] + }); + } catch (error) { + console.error('Connect integration error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const disconnectIntegration = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + const result = await query( + 'DELETE FROM integrations WHERE id = $1 AND user_id = $2 RETURNING id', + [id, req.user?.id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Integration not found' + }); + } + + res.json({ + success: true, + message: 'Integration disconnected successfully' + }); + } catch (error) { + console.error('Disconnect integration error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/logController.ts b/backend/src/controllers/logController.ts new file mode 100644 index 0000000..20ab75f --- /dev/null +++ b/backend/src/controllers/logController.ts @@ -0,0 +1,79 @@ +import { Response } from 'express'; +import { query } from '../utils/db'; +import { AuthRequest } from '../middleware/auth'; + +export const getWorkflowExecutions = async (req: AuthRequest, res: Response) => { + try { + const { workflow_id, status, limit = 100 } = req.query; + + let queryText = ` + SELECT we.id, we.workflow_id, w.name as workflow_name, we.status, we.trigger_data, + we.error_message, we.started_at, we.finished_at, we.created_at + FROM workflow_executions we + JOIN workflows w ON we.workflow_id = w.id + WHERE we.user_id = $1 + `; + const params: any[] = [req.user?.id]; + let paramCount = 1; + + if (workflow_id) { + paramCount++; + queryText += ` AND we.workflow_id = $${paramCount}`; + params.push(workflow_id); + } + + if (status) { + paramCount++; + queryText += ` AND we.status = $${paramCount}`; + params.push(status); + } + + queryText += ` ORDER BY we.created_at DESC LIMIT $${paramCount + 1}`; + params.push(limit); + + const result = await query(queryText, params); + + res.json({ + success: true, + executions: result.rows + }); + } catch (error) { + console.error('Get workflow executions error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const getExecutionDetails = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + const result = await query( + `SELECT we.*, w.name as workflow_name + FROM workflow_executions we + JOIN workflows w ON we.workflow_id = w.id + WHERE we.id = $1 AND we.user_id = $2`, + [id, req.user?.id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Execution not found' + }); + } + + res.json({ + success: true, + execution: result.rows[0] + }); + } catch (error) { + console.error('Get execution details error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/templateController.ts b/backend/src/controllers/templateController.ts new file mode 100644 index 0000000..25425fa --- /dev/null +++ b/backend/src/controllers/templateController.ts @@ -0,0 +1,198 @@ +import { Request, Response } from 'express'; +import { pool } from '../db/connection'; +import { AuthRequest } from '../middleware/auth'; +import { n8nService } from '../services/n8nService'; + +export const getTemplates = async (req: AuthRequest, res: Response) => { + try { + const { category, search } = req.query; + const userRole = req.user?.role || 'free'; + + // Build query for local templates + let queryText = ` + SELECT id, name, description, category, trigger_type, tags, is_featured, install_count, created_at + FROM templates WHERE 1=1 + `; + const params: any[] = []; + let paramCount = 0; + + if (category) { + paramCount++; + queryText += ` AND category = $${paramCount}`; + params.push(category); + } + + if (search) { + paramCount++; + queryText += ` AND (name ILIKE $${paramCount} OR description ILIKE $${paramCount})`; + params.push(`%${search}%`); + } + + queryText += ' ORDER BY is_featured DESC, install_count DESC, created_at DESC'; + + // Get local templates + const localResult = await pool.query(queryText, params); + let templates = localResult.rows.map(template => ({ + ...template, + source: 'local' + })); + + // For admin users, also fetch templates from n8n + if (userRole === 'admin') { + try { + const n8nWorkflows = await n8nService.getWorkflows(); + + const n8nTemplates = n8nWorkflows.map(workflow => ({ + id: `n8n_${workflow.id}`, + name: workflow.name, + description: `N8n workflow: ${workflow.name}`, + category: 'n8n', + trigger_type: 'n8n_workflow', + tags: ['n8n', 'automation'], + is_featured: false, + install_count: 0, + created_at: workflow.createdAt, + source: 'n8n', + n8n_workflow_id: workflow.id, + n8n_data: workflow + })); + + // Filter n8n templates if search is provided + if (search) { + const searchTerm = search.toString().toLowerCase(); + const filteredN8nTemplates = n8nTemplates.filter(template => + template.name.toLowerCase().includes(searchTerm) || + template.description.toLowerCase().includes(searchTerm) + ); + templates = [...templates, ...filteredN8nTemplates]; + } else { + templates = [...templates, ...n8nTemplates]; + } + } catch (n8nError) { + console.error('Error fetching n8n workflows:', n8nError); + // Continue with local templates only + } + } + + res.json({ + success: true, + templates: templates + }); + } catch (error) { + console.error('Get templates error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const installTemplate = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { workflow_name } = req.body; + + // Check if it's an n8n template + if (id.startsWith('n8n_')) { + const n8nWorkflowId = id.replace('n8n_', ''); + + try { + // Get the n8n workflow + const n8nWorkflow = await n8nService.getWorkflow(n8nWorkflowId); + + // Create workflow in our database that references the n8n workflow + const workflowResult = await pool.query( + `INSERT INTO workflows (user_id, name, description, trigger_type, n8n_workflow_id, status, is_active) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id`, + [ + req.user?.id, + workflow_name || n8nWorkflow.name, + `N8n workflow: ${n8nWorkflow.name}`, + 'n8n_workflow', + n8nWorkflowId, + 'connected', + n8nWorkflow.active + ] + ); + + const workflowId = workflowResult.rows[0].id; + + res.status(201).json({ + success: true, + message: 'N8n workflow connected successfully', + workflow_id: workflowId, + n8n_workflow_id: n8nWorkflowId + }); + + } catch (n8nError) { + console.error('Error connecting n8n workflow:', n8nError); + return res.status(500).json({ + success: false, + message: 'Failed to connect n8n workflow' + }); + } + } else { + // Handle local template installation + const template = await pool.query('SELECT * FROM templates WHERE id = $1', [id]); + if (template.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Template not found' + }); + } + + const templateData = template.rows[0]; + const workflowName = workflow_name || templateData.name; + + // Check user's workflow limit + const userQuery = await pool.query('SELECT workflow_limit FROM users WHERE id = $1', [req.user?.id]); + const userWorkflowLimit = userQuery.rows[0]?.workflow_limit || 3; + + const currentWorkflowsQuery = await pool.query( + 'SELECT COUNT(*) as count FROM workflows WHERE user_id = $1', + [req.user?.id] + ); + const currentWorkflowCount = parseInt(currentWorkflowsQuery.rows[0].count); + + if (userWorkflowLimit !== -1 && currentWorkflowCount >= userWorkflowLimit) { + return res.status(403).json({ + success: false, + message: 'Workflow limit reached. Please upgrade your plan to create more workflows.' + }); + } + + const workflowResult = await pool.query( + `INSERT INTO workflows (user_id, name, description, trigger_type, trigger_config, actions_config) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id`, + [req.user?.id, workflowName, templateData.description, templateData.trigger_type, + templateData.trigger_config, templateData.actions_config] + ); + + const workflowId = workflowResult.rows[0].id; + + await pool.query( + 'INSERT INTO user_templates (user_id, template_id, workflow_id) VALUES ($1, $2, $3)', + [req.user?.id, id, workflowId] + ); + + await pool.query( + 'UPDATE templates SET install_count = install_count + 1 WHERE id = $1', + [id] + ); + + res.status(201).json({ + success: true, + message: 'Template installed successfully', + workflow_id: workflowId + }); + } + } catch (error) { + console.error('Install template error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/webhookController.ts b/backend/src/controllers/webhookController.ts new file mode 100644 index 0000000..3520675 --- /dev/null +++ b/backend/src/controllers/webhookController.ts @@ -0,0 +1,167 @@ +import { Request, Response } from 'express'; +import { query } from '../utils/db'; +import { AuthRequest } from '../middleware/auth'; +import { generateRandomToken } from '../utils/auth'; + +export const getWebhooks = async (req: AuthRequest, res: Response) => { + try { + const result = await query( + `SELECT id, name, slug, trigger_type, url_path, is_active, created_at, updated_at + FROM webhooks WHERE user_id = $1 ORDER BY created_at DESC`, + [req.user?.id] + ); + + res.json({ + success: true, + webhooks: result.rows + }); + } catch (error) { + console.error('Get webhooks error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const createWebhook = async (req: AuthRequest, res: Response) => { + try { + const { name, trigger_type } = req.body; + + if (!name || !trigger_type) { + return res.status(400).json({ + success: false, + message: 'Name and trigger_type are required' + }); + } + + const slug = name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); + const secretKey = generateRandomToken(); + const urlPath = `/webhook/${req.user?.id}/${slug}`; + + const result = await query( + `INSERT INTO webhooks (user_id, name, slug, trigger_type, secret_key, url_path) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id, name, slug, trigger_type, url_path, is_active, created_at`, + [req.user?.id, name, slug, trigger_type, secretKey, urlPath] + ); + + res.status(201).json({ + success: true, + webhook: { + ...result.rows[0], + secret_key: secretKey + } + }); + } catch (error) { + console.error('Create webhook error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const getWebhookLogs = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { limit = 50 } = req.query; + + const webhook = await query( + 'SELECT id FROM webhooks WHERE id = $1 AND user_id = $2', + [id, req.user?.id] + ); + + if (webhook.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Webhook not found' + }); + } + + const result = await query( + `SELECT id, request_method, request_headers, response_status, processing_time_ms, created_at + FROM webhook_logs WHERE webhook_id = $1 ORDER BY created_at DESC LIMIT $2`, + [id, limit] + ); + + res.json({ + success: true, + logs: result.rows + }); + } catch (error) { + console.error('Get webhook logs error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const deleteWebhook = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + const result = await query( + 'DELETE FROM webhooks WHERE id = $1 AND user_id = $2 RETURNING id', + [id, req.user?.id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Webhook not found' + }); + } + + res.json({ + success: true, + message: 'Webhook deleted successfully' + }); + } catch (error) { + console.error('Delete webhook error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const handleWebhookCall = async (req: Request, res: Response) => { + try { + const { user_id, slug } = req.params; + const startTime = Date.now(); + + const webhook = await query( + 'SELECT id, secret_key FROM webhooks WHERE url_path = $1 AND is_active = true', + [`/webhook/${user_id}/${slug}`] + ); + + if (webhook.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Webhook not found' + }); + } + + const processingTime = Date.now() - startTime; + + await query( + `INSERT INTO webhook_logs (webhook_id, request_method, request_headers, request_payload, response_status, processing_time_ms) + VALUES ($1, $2, $3, $4, $5, $6)`, + [webhook.rows[0].id, req.method, req.headers, req.body, 200, processingTime] + ); + + res.json({ + success: true, + message: 'Webhook received successfully', + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('Handle webhook error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/controllers/workflowController.ts b/backend/src/controllers/workflowController.ts new file mode 100644 index 0000000..6e80f56 --- /dev/null +++ b/backend/src/controllers/workflowController.ts @@ -0,0 +1,456 @@ +import { Response } from 'express'; +import { query } from '../utils/db'; +import { AuthRequest } from '../middleware/auth'; +import n8nService from '../services/n8nService'; + +export const getWorkflows = async (req: AuthRequest, res: Response) => { + try { + // Get workflows from n8n + const n8nWorkflows = await n8nService.getWorkflows(); + + // Get local workflow metadata + const localResult = await query( + `SELECT id, name, n8n_workflow_id, description, status, trigger_type, is_active, created_at, updated_at + FROM workflows WHERE user_id = $1 ORDER BY created_at DESC`, + [req.user?.id] + ); + + // Merge n8n data with local metadata + const workflows = localResult.rows.map(localWorkflow => { + const n8nWorkflow = n8nWorkflows.find(w => w.id === localWorkflow.n8n_workflow_id); + return { + ...localWorkflow, + n8n_data: n8nWorkflow, + active: n8nWorkflow?.active || false, + status: n8nWorkflow?.active ? 'active' : 'inactive' + }; + }); + + res.json({ + success: true, + workflows, + total_n8n_workflows: n8nWorkflows.length + }); + } catch (error) { + console.error('Get workflows error:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch workflows' + }); + } +}; + +export const getWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + // Get local workflow + const localResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (localResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const localWorkflow = localResult.rows[0]; + + // Get n8n workflow if exists + let n8nWorkflow = null; + if (localWorkflow.n8n_workflow_id) { + try { + n8nWorkflow = await n8nService.getWorkflow(localWorkflow.n8n_workflow_id); + } catch (error) { + console.error('Failed to fetch n8n workflow:', error); + } + } + + res.json({ + success: true, + workflow: { + ...localWorkflow, + n8n_data: n8nWorkflow + } + }); + } catch (error) { + console.error('Get workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const createWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { name, description, trigger_type, trigger_config, actions_config, n8n_workflow_data } = req.body; + + if (!name) { + return res.status(400).json({ + success: false, + message: 'Name is required' + }); + } + + let n8nWorkflowId = null; + + // Create workflow in n8n if workflow data provided + if (n8n_workflow_data) { + try { + const n8nWorkflow = await n8nService.createWorkflow({ + name, + active: false, + nodes: n8n_workflow_data.nodes || [], + connections: n8n_workflow_data.connections || {}, + settings: n8n_workflow_data.settings, + staticData: n8n_workflow_data.staticData + }); + n8nWorkflowId = n8nWorkflow.id; + } catch (error) { + console.error('Failed to create n8n workflow:', error); + return res.status(500).json({ + success: false, + message: 'Failed to create workflow in n8n' + }); + } + } + + // Save to local database + const result = await query( + `INSERT INTO workflows (user_id, name, description, trigger_type, trigger_config, actions_config, n8n_workflow_id) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id, name, description, status, trigger_type, is_active, created_at`, + [req.user?.id, name, description, trigger_type, trigger_config, actions_config, n8nWorkflowId] + ); + + res.status(201).json({ + success: true, + workflow: { + ...result.rows[0], + n8n_workflow_id: n8nWorkflowId + } + }); + } catch (error) { + console.error('Create workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const updateWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { name, description, trigger_type, trigger_config, actions_config, n8n_workflow_data } = req.body; + + // Get existing workflow + const existingResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (existingResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const existingWorkflow = existingResult.rows[0]; + + // Update n8n workflow if exists + if (existingWorkflow.n8n_workflow_id && n8n_workflow_data) { + try { + await n8nService.updateWorkflow(existingWorkflow.n8n_workflow_id, { + name, + nodes: n8n_workflow_data.nodes, + connections: n8n_workflow_data.connections, + settings: n8n_workflow_data.settings, + staticData: n8n_workflow_data.staticData + }); + } catch (error) { + console.error('Failed to update n8n workflow:', error); + return res.status(500).json({ + success: false, + message: 'Failed to update workflow in n8n' + }); + } + } + + // Update local database + const result = await query( + `UPDATE workflows + SET name = $1, description = $2, trigger_type = $3, trigger_config = $4, actions_config = $5, updated_at = CURRENT_TIMESTAMP + WHERE id = $6 AND user_id = $7 + RETURNING id, name, description, status, trigger_type, is_active, created_at, updated_at`, + [name, description, trigger_type, trigger_config, actions_config, id, req.user?.id] + ); + + res.json({ + success: true, + workflow: result.rows[0] + }); + } catch (error) { + console.error('Update workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const activateWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + // Get workflow + const workflowResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (workflowResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const workflow = workflowResult.rows[0]; + + // Activate in n8n + if (workflow.n8n_workflow_id) { + try { + await n8nService.activateWorkflow(workflow.n8n_workflow_id); + } catch (error) { + console.error('Failed to activate n8n workflow:', error); + return res.status(500).json({ + success: false, + message: 'Failed to activate workflow in n8n' + }); + } + } + + // Update local status + await query( + `UPDATE workflows SET is_active = true, status = 'active', updated_at = CURRENT_TIMESTAMP WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + res.json({ + success: true, + message: 'Workflow activated successfully' + }); + } catch (error) { + console.error('Activate workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const deactivateWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + // Get workflow + const workflowResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (workflowResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const workflow = workflowResult.rows[0]; + + // Deactivate in n8n + if (workflow.n8n_workflow_id) { + try { + await n8nService.deactivateWorkflow(workflow.n8n_workflow_id); + } catch (error) { + console.error('Failed to deactivate n8n workflow:', error); + return res.status(500).json({ + success: false, + message: 'Failed to deactivate workflow in n8n' + }); + } + } + + // Update local status + await query( + `UPDATE workflows SET is_active = false, status = 'inactive', updated_at = CURRENT_TIMESTAMP WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + res.json({ + success: true, + message: 'Workflow deactivated successfully' + }); + } catch (error) { + console.error('Deactivate workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const executeWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const { inputData } = req.body; + + // Get workflow + const workflowResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (workflowResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const workflow = workflowResult.rows[0]; + + if (!workflow.n8n_workflow_id) { + return res.status(400).json({ + success: false, + message: 'Workflow is not connected to n8n' + }); + } + + // Execute in n8n + try { + const execution = await n8nService.executeWorkflow(workflow.n8n_workflow_id, inputData); + + // Log execution in database + await query( + `INSERT INTO workflow_executions (workflow_id, user_id, n8n_execution_id, status, trigger_data, started_at) + VALUES ($1, $2, $3, $4, $5, $6)`, + [id, req.user?.id, execution.id, execution.finished ? 'completed' : 'running', inputData, new Date()] + ); + + res.json({ + success: true, + execution + }); + } catch (error) { + console.error('Failed to execute workflow:', error); + res.status(500).json({ + success: false, + message: 'Failed to execute workflow' + }); + } + } catch (error) { + console.error('Execute workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const deleteWorkflow = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + + // Get workflow + const workflowResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (workflowResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const workflow = workflowResult.rows[0]; + + // Delete from n8n first + if (workflow.n8n_workflow_id) { + try { + await n8nService.deleteWorkflow(workflow.n8n_workflow_id); + } catch (error) { + console.error('Failed to delete n8n workflow:', error); + // Continue with local deletion even if n8n deletion fails + } + } + + // Delete from local database + await query( + 'DELETE FROM workflows WHERE id = $1 AND user_id = $2', + [id, req.user?.id] + ); + + res.json({ + success: true, + message: 'Workflow deleted successfully' + }); + } catch (error) { + console.error('Delete workflow error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; + +export const getWorkflowExecutions = async (req: AuthRequest, res: Response) => { + try { + const { id } = req.params; + const limit = parseInt(req.query.limit as string) || 20; + + // Get workflow + const workflowResult = await query( + `SELECT * FROM workflows WHERE id = $1 AND user_id = $2`, + [id, req.user?.id] + ); + + if (workflowResult.rows.length === 0) { + return res.status(404).json({ + success: false, + message: 'Workflow not found' + }); + } + + const workflow = workflowResult.rows[0]; + + if (!workflow.n8n_workflow_id) { + return res.status(400).json({ + success: false, + message: 'Workflow is not connected to n8n' + }); + } + + // Get executions from n8n + const executions = await n8nService.getExecutions(workflow.n8n_workflow_id, limit); + + res.json({ + success: true, + executions + }); + } catch (error) { + console.error('Get workflow executions error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}; \ No newline at end of file diff --git a/backend/src/db/connection.ts b/backend/src/db/connection.ts new file mode 100644 index 0000000..58deb16 --- /dev/null +++ b/backend/src/db/connection.ts @@ -0,0 +1,20 @@ +import { Pool } from 'pg'; + +const pool = new Pool({ + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432'), + database: process.env.DB_NAME || 'saas_automation', + user: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'password', +}); + +// Test connection +pool.on('connect', () => { + console.log('Connected to PostgreSQL database'); +}); + +pool.on('error', (err) => { + console.error('PostgreSQL connection error:', err); +}); + +export { pool }; \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..cdf8d64 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,713 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3001; + +app.use(helmet()); +app.use(cors()); +app.use(express.json()); + +app.get('/health', (req, res) => { + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); + +// Simple test route +app.get('/test', (req, res) => { + res.json({ message: 'Backend is working!' }); +}); + +// Simple auth endpoints for testing +app.post('/auth/login', async (req, res) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ + success: false, + message: 'Email and password are required' + }); + } + + // For now, just check against our known admin user + if (email === 'info@ai-impress.com' && password === 'admin123') { + const token = 'test-admin-token-123'; + const user = { + id: 1, + email: 'info@ai-impress.com', + first_name: 'Global', + last_name: 'Admin', + role: 'admin', + subscription_plan: 'enterprise', + subscription_status: 'active', + is_verified: true, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }; + + return res.json({ + success: true, + token, + user + }); + } + + return res.status(401).json({ + success: false, + message: 'Invalid credentials' + }); + + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}); + +app.post('/auth/signup', async (req, res) => { + try { + const { email, password, first_name, last_name } = req.body; + + if (!email || !password) { + return res.status(400).json({ + success: false, + message: 'Email and password are required' + }); + } + + // Simple mock signup + const token = 'test-user-token-456'; + const user = { + id: 2, + email, + first_name: first_name || '', + last_name: last_name || '', + role: 'free', + subscription_plan: 'free', + subscription_status: 'active', + is_verified: false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }; + + res.status(201).json({ + success: true, + token, + user + }); + + } catch (error) { + console.error('Signup error:', error); + res.status(500).json({ + success: false, + message: 'Internal server error' + }); + } +}); + +// Admin Analytics endpoint +app.get('/admin/analytics', (req, res) => { + try { + // Mock analytics data + const analytics = { + userStats: { + total_users: 156, + free_users: 120, + pro_users: 28, + enterprise_users: 6, + admin_users: 2, + verified_users: 134, + active_last_7_days: 89, + new_last_30_days: 23 + }, + workflowStats: { + total_workflows: 342, + active_workflows: 278, + new_workflows_30_days: 45, + activation_rate: 81.2 + }, + executionStats: { + total_executions: 12847, + successful_executions: 12234, + executions_24h: 234, + executions_7d: 1876 + }, + revenueStats: { + total_revenue: 18750.00, + revenue_30_days: 4200.00, + successful_payments: 34, + average_order_value: 150.00 + }, + integrationStats: [ + { provider: 'telegram', count: 45 }, + { provider: 'slack', count: 38 }, + { provider: 'gmail', count: 67 }, + { provider: 'teams', count: 23 } + ], + recentActivity: [ + { + action: 'create_workflow', + resource_type: 'workflow', + created_at: new Date(Date.now() - 1000 * 60 * 5).toISOString(), + user_email: 'john@example.com' + }, + { + action: 'upgrade_subscription', + resource_type: 'subscription', + created_at: new Date(Date.now() - 1000 * 60 * 15).toISOString(), + user_email: 'sarah@company.com' + }, + { + action: 'execute_workflow', + resource_type: 'workflow', + created_at: new Date(Date.now() - 1000 * 60 * 8).toISOString(), + user_email: 'mike@startup.io' + }, + { + action: 'create_integration', + resource_type: 'integration', + created_at: new Date(Date.now() - 1000 * 60 * 22).toISOString(), + user_email: 'lisa@agency.com' + }, + { + action: 'login', + resource_type: 'auth', + created_at: new Date(Date.now() - 1000 * 60 * 3).toISOString(), + user_email: 'admin@platform.com' + } + ], + topUsers: [ + { + email: 'power.user@company.com', + first_name: 'Alex', + last_name: 'Johnson', + subscription_plan: 'enterprise', + workflow_count: 24, + last_login_at: new Date(Date.now() - 1000 * 60 * 30).toISOString() + }, + { + email: 'busy.bee@startup.io', + first_name: 'Maria', + last_name: 'Garcia', + subscription_plan: 'pro', + workflow_count: 18, + last_login_at: new Date(Date.now() - 1000 * 60 * 120).toISOString() + }, + { + email: 'automation.expert@tech.com', + first_name: 'David', + last_name: 'Chen', + subscription_plan: 'pro', + workflow_count: 15, + last_login_at: new Date(Date.now() - 1000 * 60 * 45).toISOString() + }, + { + email: 'workflow.master@agency.com', + first_name: 'Emma', + last_name: 'Wilson', + subscription_plan: 'enterprise', + workflow_count: 12, + last_login_at: new Date(Date.now() - 1000 * 60 * 60).toISOString() + } + ], + dailyRegistrations: [ + { date: '2024-09-21', registrations: 3 }, + { date: '2024-09-22', registrations: 5 }, + { date: '2024-09-23', registrations: 2 }, + { date: '2024-09-24', registrations: 8 }, + { date: '2024-09-25', registrations: 4 }, + { date: '2024-09-26', registrations: 6 }, + { date: '2024-09-27', registrations: 7 }, + { date: '2024-09-28', registrations: 9 } + ], + executionTrend: [ + { date: '2024-09-21', executions: 145, successful: 142 }, + { date: '2024-09-22', executions: 167, successful: 163 }, + { date: '2024-09-23', executions: 132, successful: 128 }, + { date: '2024-09-24', executions: 198, successful: 189 }, + { date: '2024-09-25', executions: 156, successful: 151 }, + { date: '2024-09-26', executions: 203, successful: 197 }, + { date: '2024-09-27', executions: 178, successful: 171 }, + { date: '2024-09-28', executions: 234, successful: 227 } + ] + }; + + res.json(analytics); + } catch (error) { + console.error('Analytics error:', error); + res.status(500).json({ error: 'Failed to fetch analytics' }); + } +}); + +// Templates/Workflows endpoint (with /api prefix) +app.get('/api/templates', (req, res) => { + try { + const templates = [ + { + id: 1, + name: "Telegram to Slack Bridge", + description: "Automatically forward messages from Telegram channels to Slack channels with formatting and filtering options", + category: "Communication", + tags: ["telegram", "slack", "messaging", "automation"], + complexity: "intermediate", + estimatedTime: "15 minutes", + useCount: 234, + rating: 4.8, + author: "Aimpress Team", + thumbnail: "/templates/telegram-slack.jpg", + icon: "🔗", + gradient: "from-blue-500 to-purple-600", + workflow: { + nodes: [ + { type: "telegram-trigger", name: "Telegram Webhook" }, + { type: "filter", name: "Message Filter" }, + { type: "slack", name: "Send to Slack" } + ] + }, + createdAt: "2024-08-15T10:30:00Z", + updatedAt: "2024-09-20T14:22:00Z" + }, + { + id: 2, + name: "Gmail to Teams Notifications", + description: "Send important email notifications from Gmail to Microsoft Teams with smart filtering and priority detection", + category: "Email & Notifications", + tags: ["gmail", "teams", "notifications", "email-filter"], + complexity: "beginner", + estimatedTime: "10 minutes", + useCount: 189, + rating: 4.6, + author: "Aimpress Team", + thumbnail: "/templates/gmail-teams.jpg", + icon: "📧", + gradient: "from-red-500 to-blue-600", + workflow: { + nodes: [ + { type: "gmail-trigger", name: "Gmail Watch" }, + { type: "condition", name: "Priority Check" }, + { type: "teams", name: "Teams Message" } + ] + }, + createdAt: "2024-07-22T09:15:00Z", + updatedAt: "2024-09-18T11:45:00Z" + }, + { + id: 3, + name: "Customer Support Automation", + description: "Comprehensive customer support workflow that routes tickets, sends auto-responses, and escalates urgent issues", + category: "Customer Service", + tags: ["support", "automation", "tickets", "escalation"], + complexity: "advanced", + estimatedTime: "45 minutes", + useCount: 156, + rating: 4.9, + author: "Aimpress Team", + thumbnail: "/templates/customer-support.jpg", + icon: "🎧", + gradient: "from-green-500 to-teal-600", + workflow: { + nodes: [ + { type: "webhook", name: "Ticket Webhook" }, + { type: "ai-classifier", name: "Urgency Detection" }, + { type: "condition", name: "Route Decision" }, + { type: "email", name: "Auto Response" }, + { type: "slack", name: "Agent Alert" } + ] + }, + createdAt: "2024-06-10T16:20:00Z", + updatedAt: "2024-09-25T13:30:00Z" + }, + { + id: 4, + name: "Social Media Content Sync", + description: "Automatically sync content across multiple social media platforms with custom formatting for each platform", + category: "Social Media", + tags: ["social-media", "content", "automation", "multi-platform"], + complexity: "intermediate", + estimatedTime: "30 minutes", + useCount: 298, + rating: 4.7, + author: "Aimpress Team", + thumbnail: "/templates/social-sync.jpg", + icon: "📱", + gradient: "from-pink-500 to-orange-600", + workflow: { + nodes: [ + { type: "rss-feed", name: "Content Source" }, + { type: "ai-formatter", name: "Platform Formatter" }, + { type: "twitter", name: "Twitter Post" }, + { type: "linkedin", name: "LinkedIn Post" }, + { type: "facebook", name: "Facebook Post" } + ] + }, + createdAt: "2024-09-01T12:00:00Z", + updatedAt: "2024-09-27T10:15:00Z" + }, + { + id: 5, + name: "E-commerce Order Processing", + description: "Complete order processing pipeline with inventory checks, payment verification, and shipping notifications", + category: "E-commerce", + tags: ["orders", "inventory", "payments", "shipping"], + complexity: "advanced", + estimatedTime: "60 minutes", + useCount: 87, + rating: 4.8, + author: "Aimpress Team", + thumbnail: "/templates/ecommerce-orders.jpg", + icon: "🛒", + gradient: "from-indigo-500 to-purple-600", + workflow: { + nodes: [ + { type: "shopify-trigger", name: "New Order" }, + { type: "inventory-check", name: "Stock Verification" }, + { type: "payment-gateway", name: "Payment Check" }, + { type: "shipping-api", name: "Create Shipment" }, + { type: "email", name: "Customer Notification" } + ] + }, + createdAt: "2024-08-05T14:30:00Z", + updatedAt: "2024-09-22T16:45:00Z" + }, + { + id: 6, + name: "Data Backup & Sync", + description: "Automated data backup solution that syncs files across multiple cloud storage providers with versioning", + category: "Data Management", + tags: ["backup", "sync", "cloud-storage", "versioning"], + complexity: "intermediate", + estimatedTime: "25 minutes", + useCount: 142, + rating: 4.5, + author: "Aimpress Team", + thumbnail: "/templates/data-backup.jpg", + icon: "☁️", + gradient: "from-cyan-500 to-blue-600", + workflow: { + nodes: [ + { type: "schedule", name: "Daily Trigger" }, + { type: "google-drive", name: "Source Files" }, + { type: "dropbox", name: "Backup to Dropbox" }, + { type: "aws-s3", name: "Archive to S3" }, + { type: "slack", name: "Status Report" } + ] + }, + createdAt: "2024-07-15T11:20:00Z", + updatedAt: "2024-09-19T09:30:00Z" + } + ]; + + res.json({ + templates, + categories: ["Communication", "Email & Notifications", "Customer Service", "Social Media", "E-commerce", "Data Management"], + totalCount: templates.length + }); + } catch (error) { + console.error('Templates error:', error); + res.status(500).json({ error: 'Failed to fetch templates' }); + } +}); + +// Templates endpoint (without /api prefix for frontend compatibility) +app.get('/templates', (req, res) => { + try { + const templates = [ + { + id: 1, + name: "Telegram to Slack Bridge", + description: "Automatically forward messages from Telegram channels to Slack channels with formatting and filtering options", + category: "Communication", + tags: ["telegram", "slack", "messaging", "automation"], + complexity: "intermediate", + estimatedTime: "15 minutes", + useCount: 234, + rating: 4.8, + author: "Aimpress Team", + thumbnail: "/templates/telegram-slack.jpg", + icon: "🔗", + gradient: "from-blue-500 to-purple-600", + workflow: { + nodes: [ + { type: "telegram-trigger", name: "Telegram Webhook" }, + { type: "filter", name: "Message Filter" }, + { type: "slack", name: "Send to Slack" } + ] + }, + createdAt: "2024-08-15T10:30:00Z", + updatedAt: "2024-09-20T14:22:00Z" + }, + { + id: 2, + name: "Gmail to Teams Notifications", + description: "Send important email notifications from Gmail to Microsoft Teams with smart filtering and priority detection", + category: "Email & Notifications", + tags: ["gmail", "teams", "notifications", "email-filter"], + complexity: "beginner", + estimatedTime: "10 minutes", + useCount: 189, + rating: 4.6, + author: "Aimpress Team", + thumbnail: "/templates/gmail-teams.jpg", + icon: "📧", + gradient: "from-red-500 to-blue-600", + workflow: { + nodes: [ + { type: "gmail-trigger", name: "Gmail Watch" }, + { type: "condition", name: "Priority Check" }, + { type: "teams", name: "Teams Message" } + ] + }, + createdAt: "2024-07-22T09:15:00Z", + updatedAt: "2024-09-18T11:45:00Z" + }, + { + id: 3, + name: "Customer Support Automation", + description: "Comprehensive customer support workflow that routes tickets, sends auto-responses, and escalates urgent issues", + category: "Customer Service", + tags: ["support", "automation", "tickets", "escalation"], + complexity: "advanced", + estimatedTime: "45 minutes", + useCount: 156, + rating: 4.9, + author: "Aimpress Team", + thumbnail: "/templates/customer-support.jpg", + icon: "🎧", + gradient: "from-green-500 to-teal-600", + workflow: { + nodes: [ + { type: "webhook", name: "Ticket Webhook" }, + { type: "ai-classifier", name: "Urgency Detection" }, + { type: "condition", name: "Route Decision" }, + { type: "email", name: "Auto Response" }, + { type: "slack", name: "Agent Alert" } + ] + }, + createdAt: "2024-06-10T16:20:00Z", + updatedAt: "2024-09-25T13:30:00Z" + }, + { + id: 4, + name: "Social Media Content Sync", + description: "Automatically sync content across multiple social media platforms with custom formatting for each platform", + category: "Social Media", + tags: ["social-media", "content", "automation", "multi-platform"], + complexity: "intermediate", + estimatedTime: "30 minutes", + useCount: 298, + rating: 4.7, + author: "Aimpress Team", + thumbnail: "/templates/social-sync.jpg", + icon: "📱", + gradient: "from-pink-500 to-orange-600", + workflow: { + nodes: [ + { type: "rss-feed", name: "Content Source" }, + { type: "ai-formatter", name: "Platform Formatter" }, + { type: "twitter", name: "Twitter Post" }, + { type: "linkedin", name: "LinkedIn Post" }, + { type: "facebook", name: "Facebook Post" } + ] + }, + createdAt: "2024-09-01T12:00:00Z", + updatedAt: "2024-09-27T10:15:00Z" + }, + { + id: 5, + name: "E-commerce Order Processing", + description: "Complete order processing pipeline with inventory checks, payment verification, and shipping notifications", + category: "E-commerce", + tags: ["orders", "inventory", "payments", "shipping"], + complexity: "advanced", + estimatedTime: "60 minutes", + useCount: 87, + rating: 4.8, + author: "Aimpress Team", + thumbnail: "/templates/ecommerce-orders.jpg", + icon: "🛒", + gradient: "from-indigo-500 to-purple-600", + workflow: { + nodes: [ + { type: "shopify-trigger", name: "New Order" }, + { type: "inventory-check", name: "Stock Verification" }, + { type: "payment-gateway", name: "Payment Check" }, + { type: "shipping-api", name: "Create Shipment" }, + { type: "email", name: "Customer Notification" } + ] + }, + createdAt: "2024-08-05T14:30:00Z", + updatedAt: "2024-09-22T16:45:00Z" + }, + { + id: 6, + name: "Data Backup & Sync", + description: "Automated data backup solution that syncs files across multiple cloud storage providers with versioning", + category: "Data Management", + tags: ["backup", "sync", "cloud-storage", "versioning"], + complexity: "intermediate", + estimatedTime: "25 minutes", + useCount: 142, + rating: 4.5, + author: "Aimpress Team", + thumbnail: "/templates/data-backup.jpg", + icon: "☁️", + gradient: "from-cyan-500 to-blue-600", + workflow: { + nodes: [ + { type: "schedule", name: "Daily Trigger" }, + { type: "google-drive", name: "Source Files" }, + { type: "dropbox", name: "Backup to Dropbox" }, + { type: "aws-s3", name: "Archive to S3" }, + { type: "slack", name: "Status Report" } + ] + }, + createdAt: "2024-07-15T11:20:00Z", + updatedAt: "2024-09-19T09:30:00Z" + } + ]; + + res.json({ + templates, + categories: ["Communication", "Email & Notifications", "Customer Service", "Social Media", "E-commerce", "Data Management"], + totalCount: templates.length + }); + } catch (error) { + console.error('Templates error:', error); + res.status(500).json({ error: 'Failed to fetch templates' }); + } +}); + +// Template installation endpoint +app.post('/templates/:templateId/install', (req, res) => { + try { + const { templateId } = req.params; + const { workflow_name } = req.body; + + console.log(`Installing template ${templateId} with name: ${workflow_name}`); + + // Mock successful installation + res.json({ + success: true, + message: 'Template installed successfully', + workflow: { + id: `workflow_${Date.now()}`, + name: workflow_name, + template_id: templateId, + status: 'active', + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error('Template installation error:', error); + res.status(500).json({ + success: false, + error: 'Failed to install template' + }); + } +}); + +// Admin Users endpoint +app.get('/admin/users', (req, res) => { + try { + const mockUsers = [ + { + id: '1', + email: 'info@ai-impress.com', + first_name: 'Global', + last_name: 'Admin', + role: 'admin', + subscription_plan: 'enterprise', + subscription_status: 'active', + is_verified: true, + last_login_at: new Date(Date.now() - 1000 * 60 * 10).toISOString(), + created_at: new Date('2024-01-01').toISOString(), + workflow_count: 5 + }, + { + id: '2', + email: 'john.doe@example.com', + first_name: 'John', + last_name: 'Doe', + role: 'pro', + subscription_plan: 'pro', + subscription_status: 'active', + is_verified: true, + last_login_at: new Date(Date.now() - 1000 * 60 * 60).toISOString(), + created_at: new Date('2024-02-15').toISOString(), + workflow_count: 8 + }, + { + id: '3', + email: 'jane.smith@company.com', + first_name: 'Jane', + last_name: 'Smith', + role: 'free', + subscription_plan: 'free', + subscription_status: 'active', + is_verified: true, + last_login_at: new Date(Date.now() - 1000 * 60 * 30).toISOString(), + created_at: new Date('2024-03-10').toISOString(), + workflow_count: 3 + }, + { + id: '4', + email: 'enterprise@bigcorp.com', + first_name: 'Robert', + last_name: 'Johnson', + role: 'enterprise', + subscription_plan: 'enterprise', + subscription_status: 'active', + is_verified: true, + last_login_at: new Date(Date.now() - 1000 * 60 * 120).toISOString(), + created_at: new Date('2024-01-20').toISOString(), + workflow_count: 25 + } + ]; + + res.json({ + users: mockUsers, + pagination: { + currentPage: 1, + totalPages: 1, + totalUsers: mockUsers.length, + limit: 20 + } + }); + } catch (error) { + console.error('Users error:', error); + res.status(500).json({ error: 'Failed to fetch users' }); + } +}); + +// Update user role endpoint +app.patch('/admin/users/:userId/role', (req, res) => { + try { + const { userId } = req.params; + const { role, subscription_plan } = req.body; + + console.log(`Updating user ${userId}: role=${role}, plan=${subscription_plan}`); + + // Mock successful update + res.json({ + id: userId, + email: 'user@example.com', + role, + subscription_plan + }); + } catch (error) { + console.error('Update user role error:', error); + res.status(500).json({ error: 'Failed to update user role' }); + } +}); + +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +export default app; \ No newline at end of file diff --git a/backend/src/index_backup.ts b/backend/src/index_backup.ts new file mode 100644 index 0000000..f53a7f5 --- /dev/null +++ b/backend/src/index_backup.ts @@ -0,0 +1,28 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3001; + +app.use(helmet()); +app.use(cors()); +app.use(express.json()); + +app.get('/health', (req, res) => { + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); + +// Simple test route +app.get('/test', (req, res) => { + res.json({ message: 'Backend is working!' }); +}); + +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +export default app; \ No newline at end of file diff --git a/backend/src/index_full.ts b/backend/src/index_full.ts new file mode 100644 index 0000000..ca4bac2 --- /dev/null +++ b/backend/src/index_full.ts @@ -0,0 +1,45 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import dotenv from 'dotenv'; + +// Route imports +import authRoutes from './routes/auth'; +import workflowRoutes from './routes/workflows'; +import templateRoutes from './routes/templates'; +import integrationRoutes from './routes/integrations'; +import webhookRoutes from './routes/webhooks'; +import logRoutes from './routes/logs'; +import chatRoutes from './routes/chat'; +import adminRoutes from './routes/admin'; +import billingRoutes from './routes/billing'; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 3001; + +app.use(helmet()); +app.use(cors()); +app.use(express.json()); + +app.get('/health', (req, res) => { + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); + +// API Routes +app.use('/auth', authRoutes); +app.use('/workflows', workflowRoutes); +app.use('/templates', templateRoutes); +app.use('/integrations', integrationRoutes); +app.use('/webhooks', webhookRoutes); +app.use('/logs', logRoutes); +app.use('/chat', chatRoutes); +app.use('/admin', adminRoutes); +app.use('/billing', billingRoutes); + +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +export default app; \ No newline at end of file diff --git a/backend/src/middleware/auth.ts b/backend/src/middleware/auth.ts new file mode 100644 index 0000000..5449e74 --- /dev/null +++ b/backend/src/middleware/auth.ts @@ -0,0 +1,36 @@ +import { Request, Response, NextFunction } from 'express'; +import { verifyToken } from '../utils/auth'; + +export interface AuthRequest extends Request { + user?: { + id: number; + email: string; + role?: string; + subscription_plan?: string; + }; +} + +export const authenticateToken = (req: AuthRequest, res: Response, next: NextFunction) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + return res.status(401).json({ success: false, message: 'Access token required' }); + } + + try { + const decoded = verifyToken(token); + req.user = { + id: decoded.userId, + email: decoded.email, + role: decoded.role, + subscription_plan: decoded.subscription_plan + }; + next(); + } catch (error) { + return res.status(403).json({ success: false, message: 'Invalid or expired token' }); + } +}; + +// Alias for consistency +export const auth = authenticateToken; \ No newline at end of file diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts new file mode 100644 index 0000000..f77106b --- /dev/null +++ b/backend/src/routes/admin.ts @@ -0,0 +1,27 @@ +import express from 'express'; +import { auth } from '../middleware/auth'; +import { + getAnalytics, + getAllUsers, + updateUserRole, + getSystemSettings, + updateSystemSetting +} from '../controllers/adminController'; + +const router = express.Router(); + +// All admin routes require authentication +router.use(auth); + +// Analytics +router.get('/analytics', getAnalytics); + +// User management +router.get('/users', getAllUsers); +router.patch('/users/:userId/role', updateUserRole); + +// System settings +router.get('/settings', getSystemSettings); +router.patch('/settings/:key', updateSystemSetting); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts new file mode 100644 index 0000000..085ef15 --- /dev/null +++ b/backend/src/routes/auth.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import { signup, login, resetPassword } from '../controllers/authController'; + +const router = express.Router(); + +router.post('/signup', signup); +router.post('/login', login); +router.post('/reset-password', resetPassword); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/billing.ts b/backend/src/routes/billing.ts new file mode 100644 index 0000000..7c0a6a1 --- /dev/null +++ b/backend/src/routes/billing.ts @@ -0,0 +1,28 @@ +import express from 'express'; +import { auth } from '../middleware/auth'; +import { + getSubscriptionPlans, + getCurrentSubscription, + upgradeSubscription, + cancelSubscription, + getUsageStats, + handlePaymentWebhook, + generateInvoice +} from '../controllers/billingController'; + +const router = express.Router(); + +// Public routes +router.get('/plans', getSubscriptionPlans); +router.post('/webhook', handlePaymentWebhook); // No auth for webhooks + +// Protected routes +router.use(auth); + +router.get('/subscription', getCurrentSubscription); +router.post('/subscription/upgrade', upgradeSubscription); +router.post('/subscription/cancel', cancelSubscription); +router.get('/usage', getUsageStats); +router.get('/invoice/:paymentId', generateInvoice); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/chat.ts b/backend/src/routes/chat.ts new file mode 100644 index 0000000..32d2fd3 --- /dev/null +++ b/backend/src/routes/chat.ts @@ -0,0 +1,11 @@ +import express from 'express'; +import { sendMessage, getChatHistory } from '../controllers/chatController'; +import { authenticateToken } from '../middleware/auth'; + +const router = express.Router(); + +router.use(authenticateToken); +router.post('/', sendMessage); +router.get('/history', getChatHistory); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/integrations.ts b/backend/src/routes/integrations.ts new file mode 100644 index 0000000..53fed2a --- /dev/null +++ b/backend/src/routes/integrations.ts @@ -0,0 +1,12 @@ +import express from 'express'; +import { getIntegrations, connectIntegration, disconnectIntegration } from '../controllers/integrationController'; +import { authenticateToken } from '../middleware/auth'; + +const router = express.Router(); + +router.use(authenticateToken); +router.get('/', getIntegrations); +router.post('/:provider/connect', connectIntegration); +router.delete('/:id', disconnectIntegration); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/logs.ts b/backend/src/routes/logs.ts new file mode 100644 index 0000000..bbdb5eb --- /dev/null +++ b/backend/src/routes/logs.ts @@ -0,0 +1,11 @@ +import express from 'express'; +import { getWorkflowExecutions, getExecutionDetails } from '../controllers/logController'; +import { authenticateToken } from '../middleware/auth'; + +const router = express.Router(); + +router.use(authenticateToken); +router.get('/', getWorkflowExecutions); +router.get('/:id', getExecutionDetails); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/templates.ts b/backend/src/routes/templates.ts new file mode 100644 index 0000000..5338712 --- /dev/null +++ b/backend/src/routes/templates.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import { getTemplates, installTemplate } from '../controllers/templateController'; +import { auth } from '../middleware/auth'; + +const router = express.Router(); + +router.get('/', auth, getTemplates); +router.post('/:id/install', auth, installTemplate); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/webhooks.ts b/backend/src/routes/webhooks.ts new file mode 100644 index 0000000..de5c0bd --- /dev/null +++ b/backend/src/routes/webhooks.ts @@ -0,0 +1,16 @@ +import express from 'express'; +import { getWebhooks, createWebhook, getWebhookLogs, deleteWebhook, handleWebhookCall } from '../controllers/webhookController'; +import { authenticateToken } from '../middleware/auth'; + +const router = express.Router(); + +router.use(authenticateToken); +router.get('/', getWebhooks); +router.post('/', createWebhook); +router.get('/:id/logs', getWebhookLogs); +router.delete('/:id', deleteWebhook); + +// Public webhook endpoint +router.post('/webhook/:user_id/:slug', handleWebhookCall); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/workflows.ts b/backend/src/routes/workflows.ts new file mode 100644 index 0000000..d12edd2 --- /dev/null +++ b/backend/src/routes/workflows.ts @@ -0,0 +1,34 @@ +import express from 'express'; +import { + getWorkflows, + getWorkflow, + createWorkflow, + updateWorkflow, + deleteWorkflow, + activateWorkflow, + deactivateWorkflow, + executeWorkflow, + getWorkflowExecutions +} from '../controllers/workflowController'; +import { authenticateToken } from '../middleware/auth'; + +const router = express.Router(); + +router.use(authenticateToken); + +// Workflow CRUD +router.get('/', getWorkflows); +router.get('/:id', getWorkflow); +router.post('/', createWorkflow); +router.put('/:id', updateWorkflow); +router.delete('/:id', deleteWorkflow); + +// Workflow Actions +router.post('/:id/activate', activateWorkflow); +router.post('/:id/deactivate', deactivateWorkflow); +router.post('/:id/execute', executeWorkflow); + +// Workflow Executions +router.get('/:id/executions', getWorkflowExecutions); + +export default router; \ No newline at end of file diff --git a/backend/src/services/n8nService.ts b/backend/src/services/n8nService.ts new file mode 100644 index 0000000..303c895 --- /dev/null +++ b/backend/src/services/n8nService.ts @@ -0,0 +1,226 @@ +import axios, { AxiosInstance } from 'axios'; + +interface N8nConfig { + baseUrl: string; + apiToken: string; +} + +interface WorkflowData { + name: string; + active: boolean; + nodes: any[]; + connections: any; + settings?: any; + staticData?: any; +} + +interface N8nWorkflow { + id: string; + name: string; + active: boolean; + nodes: any[]; + connections: any; + createdAt: string; + updatedAt: string; + settings?: any; + staticData?: any; +} + +interface ExecutionResult { + id: string; + finished: boolean; + mode: string; + startedAt: string; + stoppedAt?: string; + workflowData: any; + data?: any; +} + +class N8nService { + private client: AxiosInstance; + private config: N8nConfig; + + constructor(config: N8nConfig) { + this.config = config; + this.client = axios.create({ + baseURL: `${config.baseUrl}/api/v1`, + headers: { + 'X-N8N-API-KEY': config.apiToken, + 'Content-Type': 'application/json', + }, + timeout: 30000, + }); + + // Add response interceptor for error handling + this.client.interceptors.response.use( + response => response, + error => { + console.error('N8n API Error:', error.response?.data || error.message); + throw error; + } + ); + } + + // Workflow Management + async getWorkflows(): Promise { + try { + const response = await this.client.get('/workflows'); + return response.data.data || response.data; + } catch (error) { + console.error('Failed to fetch workflows:', error); + throw new Error('Failed to fetch workflows from n8n'); + } + } + + async getWorkflow(workflowId: string): Promise { + try { + const response = await this.client.get(`/workflows/${workflowId}`); + return response.data; + } catch (error) { + console.error(`Failed to fetch workflow ${workflowId}:`, error); + throw new Error(`Failed to fetch workflow ${workflowId} from n8n`); + } + } + + async createWorkflow(workflowData: WorkflowData): Promise { + try { + const response = await this.client.post('/workflows', workflowData); + return response.data; + } catch (error) { + console.error('Failed to create workflow:', error); + throw new Error('Failed to create workflow in n8n'); + } + } + + async updateWorkflow(workflowId: string, workflowData: Partial): Promise { + try { + const response = await this.client.patch(`/workflows/${workflowId}`, workflowData); + return response.data; + } catch (error) { + console.error(`Failed to update workflow ${workflowId}:`, error); + throw new Error(`Failed to update workflow ${workflowId} in n8n`); + } + } + + async deleteWorkflow(workflowId: string): Promise { + try { + await this.client.delete(`/workflows/${workflowId}`); + } catch (error) { + console.error(`Failed to delete workflow ${workflowId}:`, error); + throw new Error(`Failed to delete workflow ${workflowId} from n8n`); + } + } + + async activateWorkflow(workflowId: string): Promise { + try { + const response = await this.client.patch(`/workflows/${workflowId}`, { active: true }); + return response.data; + } catch (error) { + console.error(`Failed to activate workflow ${workflowId}:`, error); + throw new Error(`Failed to activate workflow ${workflowId} in n8n`); + } + } + + async deactivateWorkflow(workflowId: string): Promise { + try { + const response = await this.client.patch(`/workflows/${workflowId}`, { active: false }); + return response.data; + } catch (error) { + console.error(`Failed to deactivate workflow ${workflowId}:`, error); + throw new Error(`Failed to deactivate workflow ${workflowId} in n8n`); + } + } + + // Execution Management + async executeWorkflow(workflowId: string, inputData?: any): Promise { + try { + const response = await this.client.post(`/workflows/${workflowId}/execute`, { + ...(inputData && { inputData }) + }); + return response.data; + } catch (error) { + console.error(`Failed to execute workflow ${workflowId}:`, error); + throw new Error(`Failed to execute workflow ${workflowId} in n8n`); + } + } + + async getExecutions(workflowId?: string, limit: number = 20): Promise { + try { + const params: any = { limit }; + if (workflowId) { + params.workflowId = workflowId; + } + + const response = await this.client.get('/executions', { params }); + return response.data.data || response.data; + } catch (error) { + console.error('Failed to fetch executions:', error); + throw new Error('Failed to fetch executions from n8n'); + } + } + + async getExecution(executionId: string): Promise { + try { + const response = await this.client.get(`/executions/${executionId}`); + return response.data; + } catch (error) { + console.error(`Failed to fetch execution ${executionId}:`, error); + throw new Error(`Failed to fetch execution ${executionId} from n8n`); + } + } + + async deleteExecution(executionId: string): Promise { + try { + await this.client.delete(`/executions/${executionId}`); + } catch (error) { + console.error(`Failed to delete execution ${executionId}:`, error); + throw new Error(`Failed to delete execution ${executionId} from n8n`); + } + } + + // Health and Status + async getHealth(): Promise<{ status: string }> { + try { + const response = await this.client.get('/health'); + return response.data; + } catch (error) { + console.error('Failed to check n8n health:', error); + throw new Error('Failed to check n8n health'); + } + } + + // Credentials Management (if needed) + async getCredentials(): Promise { + try { + const response = await this.client.get('/credentials'); + return response.data.data || response.data; + } catch (error) { + console.error('Failed to fetch credentials:', error); + throw new Error('Failed to fetch credentials from n8n'); + } + } + + // Test connection + async testConnection(): Promise { + try { + await this.getHealth(); + return true; + } catch (error) { + return false; + } + } +} + +// Factory function to create n8n service instance +export const createN8nService = (config: N8nConfig): N8nService => { + return new N8nService(config); +}; + +// Default instance using environment variables +export const n8nService = createN8nService({ + baseUrl: process.env.N8N_BASE_URL || 'https://bot.ai-impress.com', + apiToken: process.env.N8N_API_TOKEN || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1OWQyN2Q0ZS1jNTU3LTQxMDUtYjZmMy05Y2JmM2U5MzU1NWUiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzU5MDc1NDI4fQ.HCnDOPdq8GcphuPpz1o9871VOLbpMvd7m8C47e2Kq50', +}); + +export default n8nService; +export type { N8nWorkflow, WorkflowData, ExecutionResult, N8nConfig }; \ No newline at end of file diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts new file mode 100644 index 0000000..8639aa3 --- /dev/null +++ b/backend/src/types/index.ts @@ -0,0 +1,35 @@ +export interface User { + id: number; + email: string; + password_hash: string; + first_name?: string; + last_name?: string; + is_verified: boolean; + role: string; + verification_token?: string; + reset_password_token?: string; + reset_password_expires?: Date; + subscription_plan: string; + subscription_status: string; + created_at: Date; + updated_at: Date; +} + +export interface LoginRequest { + email: string; + password: string; +} + +export interface SignupRequest { + email: string; + password: string; + first_name?: string; + last_name?: string; +} + +export interface AuthResponse { + success: boolean; + token?: string; + user?: Omit; + message?: string; +} \ No newline at end of file diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts new file mode 100644 index 0000000..8c7cd33 --- /dev/null +++ b/backend/src/utils/auth.ts @@ -0,0 +1,26 @@ +import jwt, { SignOptions } from 'jsonwebtoken'; +import bcrypt from 'bcryptjs'; + +const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_jwt_key_change_this'; +const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h'; + +export const hashPassword = async (password: string): Promise => { + const saltRounds = 10; + return bcrypt.hash(password, saltRounds); +}; + +export const comparePassword = async (password: string, hash: string): Promise => { + return bcrypt.compare(password, hash); +}; + +export const generateToken = (payload: object): string => { + return jwt.sign(payload, JWT_SECRET, { expiresIn: '24h' }); +}; + +export const verifyToken = (token: string): any => { + return jwt.verify(token, JWT_SECRET); +}; + +export const generateRandomToken = (): string => { + return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); +}; \ No newline at end of file diff --git a/backend/src/utils/db.ts b/backend/src/utils/db.ts new file mode 100644 index 0000000..be61aeb --- /dev/null +++ b/backend/src/utils/db.ts @@ -0,0 +1,18 @@ +import { Pool } from 'pg'; + +const pool = new Pool({ + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432'), + database: process.env.DB_NAME || 'saas_automation', + user: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'password', + max: 10, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}); + +export const query = (text: string, params?: any[]) => { + return pool.query(text, params); +}; + +export default pool; \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..d1b16ac --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/database/docker-compose.yml b/database/docker-compose.yml new file mode 100644 index 0000000..47bb25c --- /dev/null +++ b/database/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_DB: saas_automation + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + networks: + - saas_network + + n8n: + image: n8nio/n8n + ports: + - "5678:5678" + environment: + - DB_TYPE=postgresdb + - DB_POSTGRESDB_HOST=postgres + - DB_POSTGRESDB_PORT=5432 + - DB_POSTGRESDB_DATABASE=n8n + - DB_POSTGRESDB_USER=postgres + - DB_POSTGRESDB_PASSWORD=password + - N8N_BASIC_AUTH_ACTIVE=true + - N8N_BASIC_AUTH_USER=admin + - N8N_BASIC_AUTH_PASSWORD=admin + volumes: + - n8n_data:/home/node/.n8n + depends_on: + - postgres + networks: + - saas_network + +volumes: + postgres_data: + n8n_data: + +networks: + saas_network: + driver: bridge \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..683e463 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,290 @@ +-- SaaS Automation Platform Database Schema + +-- Create n8n database +CREATE DATABASE n8n; + +-- Users table +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + first_name VARCHAR(100), + last_name VARCHAR(100), + is_verified BOOLEAN DEFAULT false, + verification_token VARCHAR(255), + reset_password_token VARCHAR(255), + reset_password_expires TIMESTAMP, + role VARCHAR(50) DEFAULT 'free', + subscription_plan VARCHAR(50) DEFAULT 'free', + subscription_status VARCHAR(50) DEFAULT 'active', + subscription_starts_at TIMESTAMP, + subscription_ends_at TIMESTAMP, + workflow_limit INTEGER DEFAULT 3, + user_limit INTEGER DEFAULT 1, + features JSONB DEFAULT '{"telegram_notifications": false, "email_notifications": true, "api_access": false, "premium_templates": false}', + telegram_chat_id VARCHAR(255), + notification_preferences JSONB DEFAULT '{"email": true, "telegram": false, "workflow_status": true, "system_updates": false}', + onboarding_completed BOOLEAN DEFAULT false, + last_login_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Workflows table +CREATE TABLE workflows ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + description TEXT, + n8n_workflow_id VARCHAR(255), + status VARCHAR(50) DEFAULT 'inactive', + trigger_type VARCHAR(100), + trigger_config JSONB, + actions_config JSONB, + is_active BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Index for n8n_workflow_id +CREATE INDEX idx_workflows_n8n_workflow_id ON workflows(n8n_workflow_id); + +-- Templates table (automation templates) +CREATE TABLE templates ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + category VARCHAR(100), + trigger_type VARCHAR(100), + trigger_config JSONB, + actions_config JSONB, + n8n_template JSONB, + tags TEXT[], + is_featured BOOLEAN DEFAULT false, + install_count INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- User installed templates +CREATE TABLE user_templates ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + template_id INTEGER REFERENCES templates(id) ON DELETE CASCADE, + workflow_id INTEGER REFERENCES workflows(id) ON DELETE CASCADE, + installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, template_id) +); + +-- Integrations table (OAuth connections) +CREATE TABLE integrations ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + provider VARCHAR(100) NOT NULL, + provider_user_id VARCHAR(255), + access_token TEXT, + refresh_token TEXT, + token_expires_at TIMESTAMP, + scopes TEXT[], + account_name VARCHAR(255), + account_email VARCHAR(255), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Webhooks table +CREATE TABLE webhooks ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + slug VARCHAR(255) NOT NULL, + trigger_type VARCHAR(100), + secret_key VARCHAR(255), + url_path VARCHAR(255) UNIQUE, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Webhook logs +CREATE TABLE webhook_logs ( + id SERIAL PRIMARY KEY, + webhook_id INTEGER REFERENCES webhooks(id) ON DELETE CASCADE, + request_method VARCHAR(10), + request_headers JSONB, + request_payload JSONB, + response_status INTEGER, + response_payload JSONB, + processing_time_ms INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Workflow executions/logs +CREATE TABLE workflow_executions ( + id SERIAL PRIMARY KEY, + workflow_id INTEGER REFERENCES workflows(id) ON DELETE CASCADE, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + n8n_execution_id VARCHAR(255), + status VARCHAR(50), + trigger_data JSONB, + execution_data JSONB, + error_message TEXT, + started_at TIMESTAMP, + finished_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Chat history (AI chatbot) +CREATE TABLE chat_history ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + session_id VARCHAR(255), + message_type VARCHAR(20) CHECK (message_type IN ('user', 'assistant')), + content TEXT NOT NULL, + metadata JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Indexes for performance +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_workflows_user_id ON workflows(user_id); +CREATE INDEX idx_workflows_status ON workflows(status); +CREATE INDEX idx_templates_category ON templates(category); +CREATE INDEX idx_integrations_user_id ON integrations(user_id); +CREATE INDEX idx_integrations_provider ON integrations(provider); +CREATE INDEX idx_webhooks_user_id ON webhooks(user_id); +CREATE INDEX idx_webhooks_url_path ON webhooks(url_path); +CREATE INDEX idx_webhook_logs_webhook_id ON webhook_logs(webhook_id); +CREATE INDEX idx_workflow_executions_workflow_id ON workflow_executions(workflow_id); +CREATE INDEX idx_workflow_executions_user_id ON workflow_executions(user_id); +CREATE INDEX idx_chat_history_user_id ON chat_history(user_id); +CREATE INDEX idx_chat_history_session_id ON chat_history(session_id); + +-- Subscription plans table +CREATE TABLE subscription_plans ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + price_monthly DECIMAL(10,2) NOT NULL, + price_yearly DECIMAL(10,2), + workflow_limit INTEGER NOT NULL, + user_limit INTEGER NOT NULL, + features JSONB NOT NULL, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- User subscriptions history +CREATE TABLE subscription_history ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + plan_name VARCHAR(100) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + currency VARCHAR(10) DEFAULT 'GBP', + payment_method VARCHAR(50), + payment_status VARCHAR(50) DEFAULT 'pending', + payment_provider_id VARCHAR(255), + billing_period_start TIMESTAMP NOT NULL, + billing_period_end TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- User teams table (for multi-user accounts) +CREATE TABLE user_teams ( + id SERIAL PRIMARY KEY, + owner_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + member_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role VARCHAR(50) DEFAULT 'member', + invited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + joined_at TIMESTAMP, + status VARCHAR(50) DEFAULT 'pending', + UNIQUE(owner_id, member_id) +); + +-- Notifications table +CREATE TABLE notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + type VARCHAR(50) NOT NULL, + data JSONB, + is_read BOOLEAN DEFAULT false, + sent_via VARCHAR(50), + sent_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- API tokens table +CREATE TABLE api_tokens ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + token_hash VARCHAR(255) NOT NULL, + permissions JSONB DEFAULT '["workflows:read"]', + last_used_at TIMESTAMP, + expires_at TIMESTAMP, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Audit logs table +CREATE TABLE audit_logs ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, + action VARCHAR(100) NOT NULL, + resource_type VARCHAR(50) NOT NULL, + resource_id VARCHAR(100), + details JSONB, + ip_address INET, + user_agent TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- System settings table +CREATE TABLE system_settings ( + id SERIAL PRIMARY KEY, + key VARCHAR(100) UNIQUE NOT NULL, + value JSONB NOT NULL, + description TEXT, + is_public BOOLEAN DEFAULT false, + updated_by INTEGER REFERENCES users(id), + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Sample data +INSERT INTO subscription_plans (name, price_monthly, price_yearly, workflow_limit, user_limit, features) VALUES +('Free', 0.00, 0.00, 3, 1, '{"telegram_notifications": false, "email_notifications": true, "api_access": false, "premium_templates": false, "support": "community", "execution_limit": 100}'), +('Pro', 150.00, 1500.00, 10, 2, '{"telegram_notifications": true, "email_notifications": true, "api_access": true, "premium_templates": true, "support": "email", "execution_limit": 10000}'), +('Enterprise', 0.00, 0.00, -1, -1, '{"telegram_notifications": true, "email_notifications": true, "api_access": true, "premium_templates": true, "support": "priority", "execution_limit": -1, "custom_integrations": true}'); + +INSERT INTO system_settings (key, value, description, is_public) VALUES +('trial_duration_days', '7', 'Trial period duration in days', false), +('max_workflows_free', '3', 'Maximum workflows for free users', false), +('maintenance_mode', 'false', 'System maintenance mode', true), +('welcome_message', '"Welcome to AutomationHub! Start automating your workflows today."', 'Welcome message for new users', true); + +INSERT INTO templates (name, description, category, trigger_type, trigger_config, actions_config, tags, is_featured) VALUES +('Instagram to CRM', 'Automatically sync new Instagram followers to your CRM', 'Social Media', 'instagram_follower', + '{"event": "new_follower"}', + '{"actions": [{"type": "crm_create_contact", "fields": ["username", "profile_url"]}]}', + ARRAY['instagram', 'crm', 'automation'], true), + +('Email to Task Manager', 'Create tasks from important emails', 'Productivity', 'email_received', + '{"filters": {"importance": "high"}}', + '{"actions": [{"type": "task_create", "fields": ["subject", "sender", "body"]}]}', + ARRAY['email', 'productivity', 'tasks'], true), + +('Form Submission to Slack', 'Post form submissions to Slack channel', 'Communication', 'webhook', + '{"event": "form_submit"}', + '{"actions": [{"type": "slack_message", "channel": "#leads"}]}', + ARRAY['webhook', 'slack', 'forms'], false); + +-- Insert global admin (password is 'admin123') +INSERT INTO users (email, password_hash, first_name, last_name, is_verified, role, subscription_plan, workflow_limit, user_limit, features) VALUES +('info@ai-impress.com', '$2b$10$8K1p/a0dclxKqnvzBL5TM.N7EH1Cx0m1K2oOKy2Tc2z1H5U2N3V6K', 'Global', 'Admin', true, 'admin', 'enterprise', -1, -1, '{"telegram_notifications": true, "email_notifications": true, "api_access": true, "premium_templates": true, "support": "priority", "execution_limit": -1, "admin_panel": true, "system_settings": true}'); + +-- Insert sample user (password is 'password123') +INSERT INTO users (email, password_hash, first_name, last_name, is_verified, subscription_starts_at, subscription_ends_at) VALUES +('demo@example.com', '$2b$10$8K1p/a0dclxKqnvzBL5TM.N7EH1Cx0m1K2oOKy2Tc2z1H5U2N3V6K', 'Demo', 'User', true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '7 days'); \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..d874449 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package.json and package-lock.json (if available) +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Expose port +EXPOSE 3000 + +# Start the application +CMD ["npm", "start"] \ No newline at end of file diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev new file mode 100644 index 0000000..356754e --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,18 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install all dependencies (including dev dependencies) +RUN npm install + +# Copy source code +COPY . . + +# Expose port +EXPOSE 3000 + +# Start the application in development mode +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..719cea2 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,25 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + ignores: [ + "node_modules/**", + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ], + }, +]; + +export default eslintConfig; diff --git a/frontend/next.config.ts b/frontend/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/frontend/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..9259afc --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6420 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "@headlessui/react": "^2.2.9", + "@heroicons/react": "^2.2.0", + "axios": "^1.12.2", + "lucide-react": "^0.544.0", + "next": "15.5.4", + "react": "19.1.0", + "react-dom": "19.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.5.4", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@headlessui/react": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", + "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", + "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", + "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", + "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", + "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", + "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", + "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", + "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", + "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", + "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", + "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", + "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", + "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", + "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.3" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", + "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", + "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", + "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.3" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", + "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.5.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", + "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", + "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", + "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.4.tgz", + "integrity": "sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.4.tgz", + "integrity": "sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.4.tgz", + "integrity": "sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.4.tgz", + "integrity": "sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.4.tgz", + "integrity": "sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.4.tgz", + "integrity": "sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.4.tgz", + "integrity": "sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.4.tgz", + "integrity": "sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.4.tgz", + "integrity": "sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.4.tgz", + "integrity": "sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz", + "integrity": "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.25.5", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.5.tgz", + "integrity": "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.1.tgz", + "integrity": "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz", + "integrity": "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "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" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", + "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.18", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", + "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-arm64": "4.1.13", + "@tailwindcss/oxide-darwin-x64": "4.1.13", + "@tailwindcss/oxide-freebsd-x64": "4.1.13", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", + "@tailwindcss/oxide-linux-x64-musl": "4.1.13", + "@tailwindcss/oxide-wasm32-wasi": "4.1.13", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", + "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", + "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", + "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", + "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", + "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", + "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", + "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", + "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", + "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", + "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", + "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", + "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz", + "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.13", + "@tailwindcss/oxide": "4.1.13", + "postcss": "^8.4.41", + "tailwindcss": "4.1.13" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", + "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", + "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", + "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/type-utils": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", + "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "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": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", + "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.1", + "@typescript-eslint/types": "^8.44.1", + "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 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", + "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", + "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "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 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", + "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", + "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", + "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.1", + "@typescript-eslint/tsconfig-utils": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "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 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "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": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", + "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", + "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "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.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001745", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", + "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", + "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "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": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "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.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "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.1", + "math-intrinsics": "^1.1.0", + "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.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", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.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", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.4.tgz", + "integrity": "sha512-BzgVVuT3kfJes8i2GHenC1SRJ+W3BTML11lAOYFOOPzrk2xp66jBOAGEFRw+3LkYCln5UzvFsLhojrshb5Zfaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.5.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", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "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": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "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.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.1", + "hasown": "^2.0.2", + "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.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "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.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "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": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", + "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.4.tgz", + "integrity": "sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.4", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.4", + "@next/swc-darwin-x64": "15.5.4", + "@next/swc-linux-arm64-gnu": "15.5.4", + "@next/swc-linux-arm64-musl": "15.5.4", + "@next/swc-linux-x64-gnu": "15.5.4", + "@next/swc-linux-x64-musl": "15.5.4", + "@next/swc-win32-arm64-msvc": "15.5.4", + "@next/swc-win32-x64-msvc": "15.5.4", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", + "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.0", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.4", + "@img/sharp-darwin-x64": "0.34.4", + "@img/sharp-libvips-darwin-arm64": "1.2.3", + "@img/sharp-libvips-darwin-x64": "1.2.3", + "@img/sharp-libvips-linux-arm": "1.2.3", + "@img/sharp-libvips-linux-arm64": "1.2.3", + "@img/sharp-libvips-linux-ppc64": "1.2.3", + "@img/sharp-libvips-linux-s390x": "1.2.3", + "@img/sharp-libvips-linux-x64": "1.2.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", + "@img/sharp-libvips-linuxmusl-x64": "1.2.3", + "@img/sharp-linux-arm": "0.34.4", + "@img/sharp-linux-arm64": "0.34.4", + "@img/sharp-linux-ppc64": "0.34.4", + "@img/sharp-linux-s390x": "0.34.4", + "@img/sharp-linux-x64": "0.34.4", + "@img/sharp-linuxmusl-arm64": "0.34.4", + "@img/sharp-linuxmusl-x64": "0.34.4", + "@img/sharp-wasm32": "0.34.4", + "@img/sharp-win32-arm64": "0.34.4", + "@img/sharp-win32-ia32": "0.34.4", + "@img/sharp-win32-x64": "0.34.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "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/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..99ee2d3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build --turbopack", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "@headlessui/react": "^2.2.9", + "@heroicons/react": "^2.2.0", + "axios": "^1.12.2", + "lucide-react": "^0.544.0", + "next": "15.5.4", + "react": "19.1.0", + "react-dom": "19.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.5.4", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx new file mode 100644 index 0000000..3391227 --- /dev/null +++ b/frontend/src/app/admin/page.tsx @@ -0,0 +1,505 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import api from '@/lib/api'; +import { + UsersIcon, + CogIcon, + ChartBarIcon, + CurrencyDollarIcon, + ServerIcon, + ClockIcon, + CheckCircleIcon, + XCircleIcon, + ArrowTrendingUpIcon, + ArrowTrendingDownIcon, + EyeIcon, + PencilIcon, + ShieldCheckIcon, + UserGroupIcon, + BuildingOffice2Icon, + PlayIcon, + PauseIcon, +} from '@heroicons/react/24/outline'; +import { useRouter } from 'next/navigation'; + +interface Analytics { + userStats: { + total_users: number; + free_users: number; + pro_users: number; + enterprise_users: number; + verified_users: number; + active_last_7_days: number; + new_last_30_days: number; + }; + workflowStats: { + total_workflows: number; + active_workflows: number; + new_workflows_30_days: number; + activation_rate: number; + }; + executionStats: { + total_executions: number; + successful_executions: number; + executions_24h: number; + executions_7d: number; + }; + revenueStats: { + total_revenue: number; + revenue_30_days: number; + successful_payments: number; + average_order_value: number; + }; + recentActivity: Array<{ + action: string; + resource_type: string; + created_at: string; + user_email: string; + }>; + topUsers: Array<{ + email: string; + first_name: string; + last_name: string; + subscription_plan: string; + workflow_count: number; + last_login_at: string; + }>; +} + +interface User { + id: string; + email: string; + first_name: string; + last_name: string; + role: string; + subscription_plan: string; + subscription_status: string; + is_verified: boolean; + last_login_at: string; + created_at: string; + workflow_count: number; +} + +export default function AdminDashboard() { + const { user } = useAuth(); + const router = useRouter(); + const [analytics, setAnalytics] = useState(null); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [usersLoading, setUsersLoading] = useState(false); + const [activeTab, setActiveTab] = useState('overview'); + + useEffect(() => { + if (!user || user.role !== 'admin') { + router.push('/dashboard'); + return; + } + fetchAnalytics(); + }, [user, router]); + + const fetchAnalytics = async () => { + try { + setLoading(true); + const response = await api.get('/admin/analytics'); + setAnalytics(response.data); + } catch (error) { + console.error('Error fetching analytics:', error); + } finally { + setLoading(false); + } + }; + + const fetchUsers = async () => { + try { + setUsersLoading(true); + const response = await api.get('/admin/users?limit=50'); + setUsers(response.data.users); + } catch (error) { + console.error('Error fetching users:', error); + } finally { + setUsersLoading(false); + } + }; + + const updateUserRole = async (userId: string, role: string, subscription_plan: string) => { + try { + await api.patch(`/admin/users/${userId}/role`, { role, subscription_plan }); + fetchUsers(); // Refresh users list + } catch (error) { + console.error('Error updating user role:', error); + } + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-GB', { + style: 'currency', + currency: 'GBP' + }).format(amount); + }; + + const getRoleIcon = (role: string) => { + switch (role) { + case 'admin': + return ; + case 'pro': + return ; + case 'enterprise': + return ; + default: + return ; + } + }; + + const getSubscriptionColor = (plan: string) => { + switch (plan) { + case 'enterprise': + return 'bg-purple-100 text-purple-800 border-purple-200'; + case 'pro': + return 'bg-blue-100 text-blue-800 border-blue-200'; + default: + return 'bg-gray-100 text-gray-800 border-gray-200'; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + if (!analytics) { + return ( +
+
+
+

Failed to load analytics

+ +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

Admin Dashboard

+

+ Complete system analytics and user management +

+
+ + {/* Tab Navigation */} +
+
+ + +
+
+ + {activeTab === 'overview' && ( + <> + {/* Stats Grid */} +
+ {/* User Stats */} +
+
+
+ +
+ + {analytics.userStats.total_users} + +
+

Total Users

+
+
+ Free: {analytics.userStats.free_users} + Pro: {analytics.userStats.pro_users} +
+
+ Enterprise: {analytics.userStats.enterprise_users} + Active 7d: {analytics.userStats.active_last_7_days} +
+
+
+ + {/* Workflow Stats */} +
+
+
+ +
+ + {analytics.workflowStats.total_workflows} + +
+

Workflows

+
+
+ Active: {analytics.workflowStats.active_workflows} + Rate: {Math.round(analytics.workflowStats.activation_rate)}% +
+
+ New (30d): {analytics.workflowStats.new_workflows_30_days} +
+
+
+ + {/* Execution Stats */} +
+
+
+ +
+ + {analytics.executionStats.total_executions} + +
+

Executions

+
+
+ Successful: {analytics.executionStats.successful_executions} + 24h: {analytics.executionStats.executions_24h} +
+
+ 7d: {analytics.executionStats.executions_7d} +
+
+
+ + {/* Revenue Stats */} +
+
+
+ +
+ + {formatCurrency(analytics.revenueStats.total_revenue || 0)} + +
+

Revenue

+
+
+ 30d: {formatCurrency(analytics.revenueStats.revenue_30_days || 0)} + Payments: {analytics.revenueStats.successful_payments} +
+
+ AOV: {formatCurrency(analytics.revenueStats.average_order_value || 0)} +
+
+
+
+ +
+ {/* Recent Activity */} +
+
+

Recent Activity

+
+
+
+ {analytics.recentActivity.slice(0, 10).map((activity, index) => ( +
+
+
+

+ {activity.action.replace(/_/g, ' ')} {activity.resource_type} +

+

+ {activity.user_email} • {formatDate(activity.created_at)} +

+
+
+ ))} +
+
+
+ + {/* Top Users */} +
+
+

Top Users by Workflows

+
+
+
+ {analytics.topUsers.slice(0, 10).map((user, index) => ( +
+
+
+ {user.first_name?.charAt(0) || user.email.charAt(0).toUpperCase()} +
+
+

+ {user.first_name} {user.last_name} ({user.email}) +

+

+ {user.subscription_plan} • Last login: {user.last_login_at ? formatDate(user.last_login_at) : 'Never'} +

+
+
+
+

{user.workflow_count}

+

workflows

+
+
+ ))} +
+
+
+
+ + )} + + {activeTab === 'users' && ( +
+
+

User Management

+
+
+ {usersLoading ? ( +
+
+
+ ) : ( +
+ + + + + + + + + + + + + {users.map((user) => ( + + + + + + + + + ))} + +
+ User + + Role + + Subscription + + Workflows + + Last Login + + Actions +
+
+
+ {user.first_name?.charAt(0) || user.email.charAt(0).toUpperCase()} +
+
+
+ {user.first_name} {user.last_name} +
+
{user.email}
+ {user.is_verified && ( + + )} +
+
+
+
+ {getRoleIcon(user.role)} + {user.role} +
+
+ + {user.subscription_plan} + + + {user.workflow_count} + + {user.last_login_at ? formatDate(user.last_login_at) : 'Never'} + +
+ + +
+
+
+ )} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/billing/page.tsx b/frontend/src/app/billing/page.tsx new file mode 100644 index 0000000..ddf5042 --- /dev/null +++ b/frontend/src/app/billing/page.tsx @@ -0,0 +1,474 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import api from '@/lib/api'; +import { + CreditCardIcon, + CheckIcon, + XMarkIcon, + ClockIcon, + ChartBarIcon, + DocumentIcon, + ExclamationTriangleIcon, + SparklesIcon, + UserGroupIcon, + BuildingOffice2Icon, + InformationCircleIcon, +} from '@heroicons/react/24/outline'; + +interface Plan { + id: number; + name: string; + price_monthly: number; + price_yearly: number; + workflow_limit: number; + user_limit: number; + features: any; + is_active: boolean; +} + +interface Subscription { + subscription_plan: string; + subscription_status: string; + subscription_starts_at: string; + subscription_ends_at: string; + workflow_limit: number; + user_limit: number; + features: any; + is_expired: boolean; + days_remaining: number; +} + +interface UsageStats { + limits: { + workflows: number; + teamMembers: number; + executions: number; + }; + usage: { + workflows: number; + teamMembers: number; + executions: number; + }; + percentages: { + workflows: number; + teamMembers: number; + executions: number; + }; +} + +export default function BillingPage() { + const { user } = useAuth(); + const [plans, setPlans] = useState([]); + const [subscription, setSubscription] = useState(null); + const [usage, setUsage] = useState(null); + const [loading, setLoading] = useState(true); + const [upgrading, setUpgrading] = useState(null); + const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'yearly'>('monthly'); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + try { + setLoading(true); + const [plansRes, subscriptionRes, usageRes] = await Promise.all([ + api.get('/billing/plans'), + api.get('/billing/subscription'), + api.get('/billing/usage') + ]); + + setPlans(plansRes.data.plans); + setSubscription(subscriptionRes.data.subscription); + setUsage(usageRes.data); + } catch (error) { + console.error('Error fetching billing data:', error); + } finally { + setLoading(false); + } + }; + + const upgradePlan = async (planName: string) => { + try { + setUpgrading(planName); + await api.post('/billing/subscription/upgrade', { + planName, + paymentMethod: 'manual' // In real app, this would be from payment form + }); + + // Refresh data + await fetchData(); + alert('Subscription upgraded successfully!'); + } catch (error) { + console.error('Error upgrading plan:', error); + alert('Failed to upgrade subscription. Please try again.'); + } finally { + setUpgrading(null); + } + }; + + const cancelSubscription = async () => { + if (!confirm('Are you sure you want to cancel your subscription?')) return; + + try { + await api.post('/billing/subscription/cancel'); + await fetchData(); + alert('Subscription cancelled. You will retain access until the end of your billing period.'); + } catch (error) { + console.error('Error cancelling subscription:', error); + alert('Failed to cancel subscription. Please try again.'); + } + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-GB', { + style: 'currency', + currency: 'GBP' + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + }; + + const getPlanIcon = (planName: string) => { + switch (planName.toLowerCase()) { + case 'free': + return ; + case 'pro': + return ; + case 'enterprise': + return ; + default: + return ; + } + }; + + const getPlanColor = (planName: string) => { + switch (planName.toLowerCase()) { + case 'free': + return 'from-gray-500 to-gray-600'; + case 'pro': + return 'from-blue-500 to-blue-600'; + case 'enterprise': + return 'from-purple-500 to-purple-600'; + default: + return 'from-gray-500 to-gray-600'; + } + }; + + const getUsageColor = (percentage: number) => { + if (percentage >= 90) return 'bg-red-500'; + if (percentage >= 75) return 'bg-yellow-500'; + return 'bg-green-500'; + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

Billing & Subscription

+

+ Manage your subscription and billing settings +

+
+ +
+ {/* Current Subscription & Usage */} +
+ {/* Current Plan */} + {subscription && ( +
+

Current Plan

+ +
+
+ {getPlanIcon(subscription.subscription_plan)} +
+
+

+ {subscription.subscription_plan} Plan +

+

+ {subscription.subscription_status === 'active' ? 'Active' : 'Cancelled'} +

+
+
+ + {subscription.subscription_ends_at && ( +
+ {subscription.is_expired ? ( +
+ + Subscription Expired +
+ ) : ( +
+ + + {subscription.days_remaining} days remaining + +
+ )} +

+ Expires on {formatDate(subscription.subscription_ends_at)} +

+
+ )} + + {subscription.subscription_status === 'active' && subscription.subscription_plan !== 'free' && ( + + )} +
+ )} + + {/* Usage Stats */} + {usage && ( +
+

Usage This Month

+ +
+
+
+ Workflows + + {usage.usage.workflows}/{usage.limits.workflows === -1 ? '∞' : usage.limits.workflows} + +
+
+
+
+
+ +
+
+ Team Members + + {usage.usage.teamMembers}/{usage.limits.teamMembers === -1 ? '∞' : usage.limits.teamMembers} + +
+
+
+
+
+ +
+
+ Executions + + {usage.usage.executions}/{usage.limits.executions === -1 ? '∞' : usage.limits.executions} + +
+
+
+
+
+
+
+ )} +
+ + {/* Subscription Plans */} +
+
+
+

Subscription Plans

+ +
+ + +
+
+ +
+ {plans.map((plan) => { + const isCurrentPlan = subscription?.subscription_plan === plan.name.toLowerCase(); + const price = billingPeriod === 'yearly' ? plan.price_yearly : plan.price_monthly; + const isFree = plan.name.toLowerCase() === 'free'; + + return ( +
+ {isCurrentPlan && ( +
+ + Current Plan + +
+ )} + +
+
+ {getPlanIcon(plan.name)} +
+

{plan.name}

+
+ {isFree ? ( + 'Free' + ) : plan.name.toLowerCase() === 'enterprise' ? ( + 'Contact Sales' + ) : ( + <> + {formatCurrency(price)} + + /{billingPeriod === 'yearly' ? 'year' : 'month'} + + + )} +
+ {billingPeriod === 'yearly' && !isFree && plan.name.toLowerCase() !== 'enterprise' && ( +

Save 17%

+ )} +
+ +
+
+ + + {plan.workflow_limit === -1 ? 'Unlimited' : plan.workflow_limit} workflows + +
+
+ + + {plan.user_limit === -1 ? 'Unlimited' : plan.user_limit} team members + +
+
+ + + {plan.features?.execution_limit === -1 ? 'Unlimited' : plan.features?.execution_limit} executions/month + +
+
+ {plan.features?.api_access ? ( + + ) : ( + + )} + API Access +
+
+ {plan.features?.telegram_notifications ? ( + + ) : ( + + )} + Telegram Notifications +
+
+ {plan.features?.premium_templates ? ( + + ) : ( + + )} + Premium Templates +
+
+ + {!isCurrentPlan && ( + + )} +
+ ); + })} +
+ +
+
+ +
+

Free Trial Information

+

+ All new accounts start with a 7-day free trial of Pro features. No credit card required. + You can upgrade or downgrade at any time. Enterprise customers get priority support and custom integrations. +

+
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/chat/page.tsx b/frontend/src/app/chat/page.tsx new file mode 100644 index 0000000..982c6be --- /dev/null +++ b/frontend/src/app/chat/page.tsx @@ -0,0 +1,200 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import api from '@/lib/api'; +import { ChatMessage } from '@/types'; +import { PaperAirplaneIcon } from '@heroicons/react/24/outline'; + +export default function ChatPage() { + const [messages, setMessages] = useState([]); + const [inputMessage, setInputMessage] = useState(''); + const [loading, setLoading] = useState(false); + const [sessionId, setSessionId] = useState(''); + const messagesEndRef = useRef(null); + + useEffect(() => { + fetchChatHistory(); + }, []); + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + const fetchChatHistory = async () => { + try { + const response = await api.get('/chat/history'); + setMessages(response.data.messages || []); + } catch (error) { + console.error('Error fetching chat history:', error); + } + }; + + const sendMessage = async (e: React.FormEvent) => { + e.preventDefault(); + if (!inputMessage.trim() || loading) return; + + const userMessage = inputMessage.trim(); + setInputMessage(''); + setLoading(true); + + // Add user message immediately + const newUserMessage: ChatMessage = { + message_type: 'user', + content: userMessage, + created_at: new Date().toISOString(), + }; + setMessages(prev => [...prev, newUserMessage]); + + try { + const response = await api.post('/chat', { + message: userMessage, + session_id: sessionId, + }); + + const { reply, session_id: returnedSessionId } = response.data; + + if (returnedSessionId && !sessionId) { + setSessionId(returnedSessionId); + } + + // Add assistant response + const assistantMessage: ChatMessage = { + message_type: 'assistant', + content: reply, + created_at: new Date().toISOString(), + }; + setMessages(prev => [...prev, assistantMessage]); + } catch (error: any) { + // Add error message + const errorMessage: ChatMessage = { + message_type: 'assistant', + content: 'Sorry, I encountered an error. Please try again.', + created_at: new Date().toISOString(), + }; + setMessages(prev => [...prev, errorMessage]); + } finally { + setLoading(false); + } + }; + + const suggestedQuestions = [ + 'How do I connect Instagram?', + 'How to create a webhook?', + 'My workflow is not working', + 'How do I view execution logs?', + ]; + + return ( +
+ {/* Header */} +
+

AI Assistant

+

+ Ask me anything about setting up and managing your automations. +

+
+ + {/* Messages */} +
+
+ {messages.length === 0 && ( +
+
+ 🤖 +
+

+ Hi! I'm your automation assistant +

+

+ I can help you with setting up integrations, creating workflows, troubleshooting issues, and more. +

+
+ {suggestedQuestions.map((question, index) => ( + + ))} +
+
+ )} + + {messages.map((message, index) => ( +
+
+

{message.content}

+

+ {new Date(message.created_at).toLocaleTimeString()} +

+
+
+ ))} + + {loading && ( +
+
+
+
+
+
+
+
+ AI is typing... +
+
+
+ )} + +
+
+
+ + {/* Input */} +
+
+
+ setInputMessage(e.target.value)} + placeholder="Type your question here..." + disabled={loading} + className="flex-1 border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50" + /> + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx new file mode 100644 index 0000000..87efc0f --- /dev/null +++ b/frontend/src/app/dashboard/page.tsx @@ -0,0 +1,317 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import api from '@/lib/api'; +import { Workflow } from '@/types'; +import Link from 'next/link'; +import { + PlusIcon, + PlayIcon, + PauseIcon, + CheckCircleIcon, + XCircleIcon, + ClockIcon, + SparklesIcon, + BoltIcon, + ChartBarIcon, + CogIcon as CogIconHeroicon, + ArrowTrendingUpIcon, + EyeIcon, + PencilIcon, +} from '@heroicons/react/24/outline'; + +export default function DashboardPage() { + const { user } = useAuth(); + const [workflows, setWorkflows] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchWorkflows(); + }, []); + + const fetchWorkflows = async () => { + try { + const response = await api.get('/workflows'); + setWorkflows(response.data.workflows || []); + } catch (error) { + console.error('Error fetching workflows:', error); + } finally { + setLoading(false); + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'active': + return ; + case 'inactive': + return ; + case 'error': + return ; + default: + return ; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'active': + return 'text-green-700 bg-green-100 border-green-200'; + case 'inactive': + return 'text-gray-700 bg-gray-100 border-gray-200'; + case 'error': + return 'text-red-700 bg-red-100 border-red-200'; + default: + return 'text-yellow-700 bg-yellow-100 border-yellow-200'; + } + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+
+
+

+ Welcome back, {user?.first_name || user?.email?.split('@')[0]}! 👋 +

+

+ Here's what's happening with your automations today +

+
+ + + Create Workflow + + +
+
+ + {/* Stats Cards */} +
+
+
+
+

Total Workflows

+

{workflows.length}

+

+2 this week

+
+
+ +
+
+
+ +
+
+
+

Active Workflows

+

+ {workflows.filter(w => w.is_active).length} +

+

Running smoothly

+
+
+ +
+
+
+ +
+
+
+

Monthly Executions

+

1,247

+

↗ +18% vs last month

+
+
+ +
+
+
+ +
+
+
+

Current Plan

+

+ {user?.subscription_plan} +

+

Unlimited workflows

+
+
+ +
+
+
+
+ + {/* Quick Actions */} +
+ +
+
+ +
+
+

+ Browse Templates +

+

+ Ready-made automation templates +

+
+
+ + + +
+
+ +
+
+

+ Connect Apps +

+

+ Integrate with your favorite tools +

+
+
+ + + +
+
+ +
+
+

+ AI Assistant +

+

+ Get help building workflows +

+
+
+ +
+ + {/* Workflows Section */} +
+
+
+
+

Your Workflows

+

Manage and monitor your automations

+
+
+ + + +
+
+
+ +
+ {workflows.length === 0 ? ( +
+
+ +
+

No workflows yet

+

+ Get started by creating your first automation workflow. Choose from templates or build from scratch. +

+
+ + + Browse Templates + + + + Create From Scratch + +
+
+ ) : ( +
+ {workflows.map((workflow) => ( +
+
+
+
+ {getStatusIcon(workflow.status)} +
+
+

+ {workflow.name} +

+

+ {workflow.description} +

+
+
+ + {workflow.status} + +
+ +
+
+ + {workflow.trigger_type} + + Last run: 2h ago +
+
+ + + + +
+
+
+ ))} +
+ )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/frontend/src/app/favicon.ico differ diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000..8e94523 --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,128 @@ +@import "tailwindcss"; + +:root { + /* n8n.io inspired color palette */ + --background: #f8f9fa; + --foreground: #2f3349; + --card: #ffffff; + --card-foreground: #2f3349; + --primary: #ff6d5a; + --primary-foreground: #ffffff; + --secondary: #5865f2; + --secondary-foreground: #ffffff; + --accent: #00d4aa; + --accent-foreground: #ffffff; + --muted: #f1f3f5; + --muted-foreground: #6c757d; + --border: #e5e9f0; + --input: #ffffff; + --ring: #ff6d5a; + + /* Success, warning, error colors in n8n style */ + --success: #00d4aa; + --warning: #ffb800; + --error: #ff4757; + + /* Gradient colors for modern look */ + --gradient-start: #ff6d5a; + --gradient-end: #5865f2; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-success: var(--success); + --color-warning: var(--warning); + --color-error: var(--error); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #1e1e2e; + --foreground: #cdd6f4; + --card: #282a36; + --card-foreground: #cdd6f4; + --primary: #ff6d5a; + --primary-foreground: #ffffff; + --secondary: #5865f2; + --secondary-foreground: #ffffff; + --accent: #00d4aa; + --accent-foreground: #ffffff; + --muted: #383a59; + --muted-foreground: #9399b2; + --border: #44475a; + --input: #282a36; + --ring: #ff6d5a; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--font-sans), -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; +} + +/* Custom gradients and animations */ +.gradient-primary { + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); +} + +.gradient-text { + background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.animate-float { + animation: float 6s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } +} + +.glass-effect { + backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: var(--muted); +} + +::-webkit-scrollbar-thumb { + background: var(--primary); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--secondary); +} diff --git a/frontend/src/app/integrations/page.tsx b/frontend/src/app/integrations/page.tsx new file mode 100644 index 0000000..2ba0180 --- /dev/null +++ b/frontend/src/app/integrations/page.tsx @@ -0,0 +1,334 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import api from '@/lib/api'; +import { Integration } from '@/types'; +import { + LinkIcon, + CheckCircleIcon, + XCircleIcon, + SparklesIcon, + BoltIcon, + ChatBubbleLeftIcon, + EnvelopeIcon, + UserGroupIcon, + BuildingOfficeIcon, + GlobeAltIcon, + CameraIcon, + ShoppingBagIcon, + CalendarIcon +} from '@heroicons/react/24/outline'; + +const availableProviders = [ + { + id: 'gmail', + name: 'Gmail', + description: 'Access your Gmail inbox, send emails, and automate email workflows', + icon: EnvelopeIcon, + color: 'from-red-500 to-red-600', + category: 'Email' + }, + { + id: 'slack', + name: 'Slack', + description: 'Send messages, notifications, and manage team communications', + icon: ChatBubbleLeftIcon, + color: 'from-purple-500 to-purple-600', + category: 'Communication' + }, + { + id: 'telegram', + name: 'Telegram', + description: 'Send messages to channels and groups, manage bots', + icon: ChatBubbleLeftIcon, + color: 'from-blue-500 to-blue-600', + category: 'Communication' + }, + { + id: 'teams', + name: 'Microsoft Teams', + description: 'Collaborate with your team and automate meeting workflows', + icon: UserGroupIcon, + color: 'from-blue-600 to-indigo-600', + category: 'Communication' + }, + { + id: 'instagram', + name: 'Instagram', + description: 'Manage your Instagram business account and automate posts', + icon: CameraIcon, + color: 'from-pink-500 to-purple-600', + category: 'Social Media' + }, + { + id: 'linkedin', + name: 'LinkedIn', + description: 'Manage your LinkedIn connections, posts, and professional network', + icon: UserGroupIcon, + color: 'from-blue-700 to-blue-800', + category: 'Social Media' + }, + { + id: 'shopify', + name: 'Shopify', + description: 'Automate your e-commerce store operations and order processing', + icon: ShoppingBagIcon, + color: 'from-green-500 to-green-600', + category: 'E-commerce' + }, + { + id: 'zoho', + name: 'Zoho CRM', + description: 'Sync contacts, deals, and automate your customer relationship workflows', + icon: BuildingOfficeIcon, + color: 'from-orange-500 to-red-600', + category: 'CRM' + }, + { + id: 'calendly', + name: 'Calendly', + description: 'Automate scheduling and meeting management workflows', + icon: CalendarIcon, + color: 'from-blue-500 to-cyan-600', + category: 'Productivity' + }, + { + id: 'webhook', + name: 'Webhooks', + description: 'Connect any service with custom HTTP webhooks and API calls', + icon: GlobeAltIcon, + color: 'from-gray-500 to-gray-600', + category: 'Developer' + } +]; + +export default function IntegrationsPage() { + const [integrations, setIntegrations] = useState([]); + const [loading, setLoading] = useState(true); + const [connecting, setConnecting] = useState(null); + + useEffect(() => { + fetchIntegrations(); + }, []); + + const fetchIntegrations = async () => { + try { + const response = await api.get('/integrations'); + setIntegrations(response.data.integrations || []); + } catch (error) { + console.error('Error fetching integrations:', error); + } finally { + setLoading(false); + } + }; + + const connectProvider = async (providerId: string) => { + setConnecting(providerId); + try { + // Mock OAuth flow - in real implementation, this would redirect to OAuth provider + const mockToken = 'mock_access_token_' + Math.random().toString(36).substring(7); + + await api.post(`/integrations/${providerId}/connect`, { + access_token: mockToken, + account_name: `Mock ${providerId} Account`, + account_email: `user@${providerId}.com`, + scopes: ['read', 'write'] + }); + + await fetchIntegrations(); + } catch (error: any) { + alert(error.response?.data?.message || 'Failed to connect integration'); + } finally { + setConnecting(null); + } + }; + + const disconnectIntegration = async (integrationId: number) => { + if (!confirm('Are you sure you want to disconnect this integration?')) return; + + try { + await api.delete(`/integrations/${integrationId}`); + await fetchIntegrations(); + } catch (error: any) { + alert(error.response?.data?.message || 'Failed to disconnect integration'); + } + }; + + const getConnectedIntegration = (providerId: string) => { + return integrations.find(int => int.provider === providerId); + }; + + if (loading) { + return ( +
+
+
+ ); + } + + const categories = [...new Set(availableProviders.map(p => p.category))]; + + return ( +
+
+
+

Integrations

+

+ Connect your favorite apps and services to create powerful automation workflows. +

+
+ + {/* Categories Overview */} +
+ {categories.map((category) => { + const categoryCount = availableProviders.filter(p => p.category === category).length; + return ( +
+
+ +
+

{category}

+

{categoryCount} apps

+
+ ); + })} +
+ + {/* Integrations Grid */} +
+ {availableProviders.map((provider) => { + const connectedIntegration = getConnectedIntegration(provider.id); + const isConnected = !!connectedIntegration; + const IconComponent = provider.icon; + + return ( +
+ {/* Header */} +
+
+
+
+ +
+
+

+ {provider.name} +

+ + {provider.category} + +
+
+
+ {isConnected ? ( + <> + + Connected + + ) : ( + <> + + Available + + )} +
+
+ +

+ {provider.description} +

+
+ + {/* Footer */} +
+ {isConnected ? ( +
+
+
+

+ + Connected as: +

+

{connectedIntegration.account_name}

+

+ Connected on {new Date(connectedIntegration.created_at).toLocaleDateString()} +

+
+
+ +
+ ) : ( + + )} +
+
+ ); + })} +
+ + {/* Info Section */} +
+
+
+ +
+

Ready to Automate?

+

+ Connect your apps and start building powerful automation workflows in minutes. + No coding required – just point, click, and automate! +

+
+
+
+ +
+

Easy Setup

+

Connect in seconds with OAuth authentication

+
+
+
+ +
+

Secure

+

Enterprise-grade security for all connections

+
+
+
+ +
+

Powerful

+

Create complex workflows with simple drag & drop

+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..f61880e --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; +import ClientProvider from "@/components/ClientProvider"; +import Layout from "@/components/Layout"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Aimpress AutomationHub - SaaS Automation Platform", + description: "Build and manage your automation workflows with ease", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx new file mode 100644 index 0000000..15b9bbb --- /dev/null +++ b/frontend/src/app/login/page.tsx @@ -0,0 +1,206 @@ +'use client'; + +import { useState } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { EyeIcon, EyeSlashIcon, BoltIcon, ArrowRightIcon } from '@heroicons/react/24/outline'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const { login } = useAuth(); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + await login(email, password); + router.push('/dashboard'); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ {/* Logo and Back to Home */} +
+ + + Back to Home + +
+
+ +
+ AutomationHub +
+

+ Welcome back +

+

+ Sign in to continue building amazing automations +

+
+ + {/* Login Form */} +
+
+ {error && ( +
+ {error} +
+ )} + +
+
+ + setEmail(e.target.value)} + className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all text-gray-900 placeholder-gray-500" + placeholder="Enter your email" + /> +
+ +
+ +
+ setPassword(e.target.value)} + className="w-full px-4 py-3 pr-12 border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent transition-all text-gray-900 placeholder-gray-500" + placeholder="Enter your password" + /> + +
+
+
+ +
+
+ + +
+ + Forgot password? + +
+ + + +
+
+
+
+
+ Or continue with +
+
+ +
+ + + +
+ + +
+

+ Don't have an account?{' '} + + Sign up for free + +

+
+
+ + {/* Demo Credentials */} +
+

Demo Credentials

+

+ Email: demo@example.com
+ Password: password123 +

+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/marketplace/page.tsx b/frontend/src/app/marketplace/page.tsx new file mode 100644 index 0000000..bf6ce38 --- /dev/null +++ b/frontend/src/app/marketplace/page.tsx @@ -0,0 +1,442 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import api from '@/lib/api'; +import { + MagnifyingGlassIcon, + SparklesIcon, + TagIcon, + UserIcon, + ClockIcon, + ArrowDownIcon, + CheckCircleIcon, + ShieldCheckIcon, + CogIcon, + BoltIcon, + CloudIcon, + LockClosedIcon, +} from '@heroicons/react/24/outline'; + +interface Template { + id: string | number; + name: string; + description: string; + category: string; + trigger_type?: string; + tags: string[]; + is_featured?: boolean; + install_count?: number; + useCount?: number; + rating?: number; + author?: string; + icon?: string; + gradient?: string; + complexity?: string; + estimatedTime?: string; + created_at?: string; + createdAt?: string; + source?: 'local' | 'n8n'; + n8n_workflow_id?: string; + n8n_data?: any; + workflow?: any; +} + +export default function MarketplacePage() { + const { user } = useAuth(); + const [templates, setTemplates] = useState([]); + const [filteredTemplates, setFilteredTemplates] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('all'); + const [installing, setInstalling] = useState(null); + + const categories = [ + { value: 'all', label: 'All Categories', icon: SparklesIcon }, + { value: 'Social Media', label: 'Social Media', icon: UserIcon }, + { value: 'Productivity', label: 'Productivity', icon: ClockIcon }, + { value: 'Communication', label: 'Communication', icon: BoltIcon }, + { value: 'n8n', label: 'N8n Workflows', icon: CloudIcon }, + ]; + + useEffect(() => { + fetchTemplates(); + }, []); + + useEffect(() => { + filterTemplates(); + }, [templates, searchTerm, selectedCategory]); + + const fetchTemplates = async () => { + try { + setLoading(true); + const response = await api.get('/templates'); + setTemplates(response.data.templates || []); + } catch (error) { + console.error('Error fetching templates:', error); + } finally { + setLoading(false); + } + }; + + const filterTemplates = () => { + let filtered = templates; + + if (searchTerm) { + filtered = filtered.filter(template => + template.name.toLowerCase().includes(searchTerm.toLowerCase()) || + template.description.toLowerCase().includes(searchTerm.toLowerCase()) || + template.tags?.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + ); + } + + if (selectedCategory !== 'all') { + filtered = filtered.filter(template => template.category === selectedCategory); + } + + setFilteredTemplates(filtered); + }; + + const installTemplate = async (templateId: string, templateName: string) => { + try { + setInstalling(templateId); + const response = await api.post(`/templates/${templateId}/install`, { + workflow_name: templateName + }); + + if (response.data.success) { + alert(`${templateName} installed successfully!`); + } + } catch (error: any) { + console.error('Error installing template:', error); + if (error.response?.status === 403) { + alert('Workflow limit reached. Please upgrade your plan to install more templates.'); + } else { + alert('Failed to install template. Please try again.'); + } + } finally { + setInstalling(null); + } + }; + + const getSourceIcon = (template: Template) => { + if (template.source === 'n8n') { + return ; + } + return ; + }; + + const getSourceBadge = (template: Template) => { + if (template.source === 'n8n') { + return ( + + + N8n + + ); + } + return ( + + + Local + + ); + }; + + const canAccessTemplate = (template: Template) => { + // N8n templates are only available to admin users + if (template.source === 'n8n') { + return user?.role === 'admin'; + } + return true; + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }); + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

Template Marketplace

+

+ Discover and install automation templates to jumpstart your workflows +

+
+ + {/* Search and Filters */} +
+
+ {/* Search */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-transparent" + /> +
+
+ + {/* Category Filter */} +
+ +
+
+ + {/* Stats */} +
+

+ Showing {filteredTemplates.length} of {templates.length} templates +

+ {user?.role === 'admin' && ( +
+ + Admin: N8n templates available +
+ )} +
+
+ + {/* Templates Grid */} +
+ {filteredTemplates.map((template) => { + const hasAccess = canAccessTemplate(template); + + return ( +
+ {/* Header */} +
+
+
+
+ {template.icon || getSourceIcon(template)} +
+
+

{template.name}

+
+ {getSourceBadge(template)} + {template.is_featured && ( + + + Featured + + )} +
+
+
+ + {!hasAccess && ( + + )} +
+ +

+ {template.description} +

+ + {/* Rating and Stats */} +
+ {template.rating && ( +
+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+ + {template.rating.toFixed(1)} + +
+ )} + {template.complexity && ( + + {template.complexity} + + )} + {template.estimatedTime && ( + + + {template.estimatedTime} + + )} +
+ + {/* Tags */} + {template.tags && template.tags.length > 0 && ( +
+ {template.tags.slice(0, 3).map((tag) => ( + + + {tag} + + ))} + {template.tags.length > 3 && ( + + +{template.tags.length - 3} more + + )} +
+ )} +
+ + {/* Footer */} +
+
+
+ + + {template.useCount || template.install_count || 0} uses + + {(template.created_at || template.createdAt) && ( + + + {formatDate(template.created_at || template.createdAt!)} + + )} + {template.author && ( + + + {template.author} + + )} +
+
+ + {hasAccess ? ( + + ) : ( +
+
+ + Admin Access Required +
+
+ )} +
+
+ ); + })} +
+ + {/* Empty State */} + {filteredTemplates.length === 0 && ( +
+
+ +
+

No templates found

+

+ {searchTerm || selectedCategory !== 'all' + ? 'Try adjusting your search or filter criteria.' + : 'There are no templates available at the moment.'} +

+ {(searchTerm || selectedCategory !== 'all') && ( + + )} +
+ )} + + {/* Info Card */} +
+
+
+ +
+
+

About Templates

+

+ Templates are pre-built automation workflows that you can install and customize for your needs. + They include pre-configured triggers, actions, and integrations to help you get started quickly. +

+ {user?.role === 'admin' && ( +

+ Admin Access: You can access N8n workflows directly from your connected N8n instance. + These appear as templates and can be connected to your automation platform. +

+ )} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..553ee10 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,250 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAuth } from '@/hooks/useAuth'; +import Link from 'next/link'; +import { + ArrowRightIcon, + SparklesIcon, + BoltIcon, + ShieldCheckIcon, + CpuChipIcon, + PlayIcon, + CheckIcon, +} from '@heroicons/react/24/outline'; + +export default function Home() { + const { user, loading } = useAuth(); + const router = useRouter(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + if (!loading && user) { + router.push('/dashboard'); + } + }, [user, loading, router]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (!mounted) return null; + + return ( +
+ {/* Navigation */} + + + {/* Hero Section */} +
+
+

+ Automate Everything +
+ Without Code +

+

+ Connect your apps, automate workflows, and boost productivity with our + visual automation platform. No coding required. +

+
+ + Start Building Free + + + + + Watch Demo + +
+ + {/* Floating Cards */} +
+
+ +

Visual Builder

+

Drag and drop to create powerful automations

+
+
+ +

Lightning Fast

+

Execute workflows in seconds, not hours

+
+
+ +

Secure & Reliable

+

Enterprise-grade security and uptime

+
+
+
+
+ + {/* Features Section */} +
+
+
+

+ Everything you need to automate +

+

+ From simple tasks to complex workflows, our platform scales with your needs +

+
+ +
+ {[ + { + icon: CpuChipIcon, + title: '500+ Integrations', + description: 'Connect with all your favorite apps and services', + color: 'text-blue-500', + }, + { + icon: BoltIcon, + title: 'Real-time Triggers', + description: 'Instant responses to events across your stack', + color: 'text-orange-500', + }, + { + icon: SparklesIcon, + title: 'AI-Powered', + description: 'Smart suggestions and automated optimizations', + color: 'text-purple-500', + }, + { + icon: ShieldCheckIcon, + title: 'Enterprise Security', + description: 'SOC 2 compliant with end-to-end encryption', + color: 'text-green-500', + }, + { + icon: PlayIcon, + title: 'One-Click Deploy', + description: 'Deploy automations instantly with zero downtime', + color: 'text-red-500', + }, + { + icon: CheckIcon, + title: '99.9% Uptime', + description: 'Reliable execution you can count on', + color: 'text-teal-500', + }, + ].map((feature, index) => ( +
+ +

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ + {/* CTA Section */} +
+
+

+ Ready to transform your workflow? +

+

+ Join thousands of teams already automating their processes +

+ + Start Your Free Trial + + +
+
+ + {/* Footer */} +
+
+
+
+
+
+ +
+ Aimpress AutomationHub +
+

+ The visual automation platform for modern teams. +

+
+
+

Product

+
    +
  • Features
  • +
  • Integrations
  • +
  • Templates
  • +
  • Pricing
  • +
+
+
+

Support

+
    +
  • Documentation
  • +
  • Help Center
  • +
  • Community
  • +
  • Contact
  • +
+
+
+

Company

+
    +
  • About
  • +
  • Blog
  • +
  • Careers
  • +
  • Privacy
  • +
+
+
+
+

© 2025 Aimpress LTD. All rights reserved.

+
+
+
+
+ ); +} diff --git a/frontend/src/app/signup/page.tsx b/frontend/src/app/signup/page.tsx new file mode 100644 index 0000000..512a1fa --- /dev/null +++ b/frontend/src/app/signup/page.tsx @@ -0,0 +1,109 @@ +'use client'; + +import { useState } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; + +export default function SignupPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const { signup } = useAuth(); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + await signup(email, password, firstName, lastName); + router.push('/dashboard'); + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

+ Create your account +

+

+ Or{' '} + + sign in to your existing account + +

+
+
+ {error && ( +
+ {error} +
+ )} +
+
+
+ setFirstName(e.target.value)} + className="block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + placeholder="First name" + /> +
+
+ setLastName(e.target.value)} + className="block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + placeholder="Last name" + /> +
+
+
+ setEmail(e.target.value)} + className="block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + placeholder="Email address" + /> +
+
+ setPassword(e.target.value)} + className="block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" + placeholder="Password" + /> +
+
+ +
+ +
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/workflows/page.tsx b/frontend/src/app/workflows/page.tsx new file mode 100644 index 0000000..3ace9da --- /dev/null +++ b/frontend/src/app/workflows/page.tsx @@ -0,0 +1,396 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAuth } from '@/hooks/useAuth'; +import api from '@/lib/api'; +import Link from 'next/link'; +import { + PlusIcon, + PlayIcon, + PauseIcon, + CheckCircleIcon, + XCircleIcon, + ClockIcon, + SparklesIcon, + BoltIcon, + ChartBarIcon, + CogIcon, + ArrowTrendingUpIcon, + EyeIcon, + PencilIcon, + TrashIcon, + ArrowPathIcon, +} from '@heroicons/react/24/outline'; + +interface Workflow { + id: string; + name: string; + description: string; + status: string; + trigger_type: string; + is_active: boolean; + n8n_workflow_id?: string; + n8n_data?: any; + created_at: string; + updated_at: string; +} + +interface Execution { + id: string; + finished: boolean; + mode: string; + startedAt: string; + stoppedAt?: string; +} + +export default function WorkflowsPage() { + const { user } = useAuth(); + const [workflows, setWorkflows] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedWorkflow, setSelectedWorkflow] = useState(null); + const [executions, setExecutions] = useState([]); + const [executionsLoading, setExecutionsLoading] = useState(false); + + useEffect(() => { + fetchWorkflows(); + }, []); + + const fetchWorkflows = async () => { + try { + setLoading(true); + const response = await api.get('/workflows'); + setWorkflows(response.data.workflows || []); + } catch (error) { + console.error('Error fetching workflows:', error); + } finally { + setLoading(false); + } + }; + + const fetchExecutions = async (workflowId: string) => { + try { + setExecutionsLoading(true); + const response = await api.get(`/workflows/${workflowId}/executions`); + setExecutions(response.data.executions || []); + } catch (error) { + console.error('Error fetching executions:', error); + } finally { + setExecutionsLoading(false); + } + }; + + const activateWorkflow = async (workflowId: string) => { + try { + await api.post(`/workflows/${workflowId}/activate`); + fetchWorkflows(); // Refresh workflows + } catch (error) { + console.error('Error activating workflow:', error); + } + }; + + const deactivateWorkflow = async (workflowId: string) => { + try { + await api.post(`/workflows/${workflowId}/deactivate`); + fetchWorkflows(); // Refresh workflows + } catch (error) { + console.error('Error deactivating workflow:', error); + } + }; + + const executeWorkflow = async (workflowId: string) => { + try { + await api.post(`/workflows/${workflowId}/execute`); + // Refresh executions if this workflow is selected + if (selectedWorkflow === workflowId) { + fetchExecutions(workflowId); + } + } catch (error) { + console.error('Error executing workflow:', error); + } + }; + + const deleteWorkflow = async (workflowId: string) => { + if (!confirm('Are you sure you want to delete this workflow?')) return; + + try { + await api.delete(`/workflows/${workflowId}`); + fetchWorkflows(); // Refresh workflows + if (selectedWorkflow === workflowId) { + setSelectedWorkflow(null); + setExecutions([]); + } + } catch (error) { + console.error('Error deleting workflow:', error); + } + }; + + const getStatusIcon = (workflow: Workflow) => { + if (workflow.n8n_data?.active || workflow.is_active) { + return ; + } + return ; + }; + + const getStatusColor = (workflow: Workflow) => { + if (workflow.n8n_data?.active || workflow.is_active) { + return 'text-green-700 bg-green-100 border-green-200'; + } + return 'text-gray-700 bg-gray-100 border-gray-200'; + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+
+
+

+ Workflows +

+

+ Manage your automation workflows connected to n8n +

+
+
+ + + + Create Workflow + + +
+
+
+ +
+ {/* Workflows List */} +
+
+
+

+ Your Workflows ({workflows.length}) +

+
+ +
+ {workflows.length === 0 ? ( +
+
+ +
+

No workflows yet

+

+ Get started by creating your first automation workflow connected to n8n. +

+ + + Create Your First Workflow + +
+ ) : ( +
+ {workflows.map((workflow) => ( +
{ + setSelectedWorkflow(workflow.id); + fetchExecutions(workflow.id); + }} + > +
+
+
+ {getStatusIcon(workflow)} +
+
+

+ {workflow.name} +

+

+ {workflow.description} +

+ {workflow.n8n_workflow_id && ( +

+ Connected to n8n: {workflow.n8n_workflow_id} +

+ )} +
+
+ + {workflow.n8n_data?.active || workflow.is_active ? 'Active' : 'Inactive'} + +
+ +
+
+ + {workflow.trigger_type || 'Manual'} + + Updated: {formatDate(workflow.updated_at)} +
+
+ + {workflow.n8n_data?.active || workflow.is_active ? ( + + ) : ( + + )} + e.stopPropagation()} + > + + + +
+
+
+ ))} +
+ )} +
+
+
+ + {/* Executions Panel */} +
+
+
+

+ {selectedWorkflow ? 'Recent Executions' : 'Select Workflow'} +

+
+ +
+ {!selectedWorkflow ? ( +
+ +

+ Select a workflow to view its execution history +

+
+ ) : executionsLoading ? ( +
+
+
+ ) : executions.length === 0 ? ( +
+ +

+ No executions found for this workflow +

+
+ ) : ( +
+ {executions.map((execution) => ( +
+
+
+ {execution.finished ? ( + + ) : ( + + )} + + {execution.finished ? 'Completed' : 'Running'} + +
+ + {formatDate(execution.startedAt)} + +
+
+ Mode: {execution.mode} + {execution.stoppedAt && ( + + • Duration: {Math.round((new Date(execution.stoppedAt).getTime() - new Date(execution.startedAt).getTime()) / 1000)}s + + )} +
+
+ ))} +
+ )} +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/ClientProvider.tsx b/frontend/src/components/ClientProvider.tsx new file mode 100644 index 0000000..f89bc75 --- /dev/null +++ b/frontend/src/components/ClientProvider.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { AuthProvider } from '@/hooks/useAuth'; + +export default function ClientProvider({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..ec0385e --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,176 @@ +'use client'; + +import { useAuth } from '@/hooks/useAuth'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { + HomeIcon, + CogIcon, + Squares2X2Icon, + LinkIcon, + DocumentIcon, + ChatBubbleLeftIcon, + ArrowRightOnRectangleIcon, + BoltIcon, + ChartBarIcon, + SparklesIcon, + UserCircleIcon, + ShieldCheckIcon, + CreditCardIcon, +} from '@heroicons/react/24/outline'; + +interface LayoutProps { + children: React.ReactNode; +} + +const navigation = [ + { name: 'Dashboard', href: '/dashboard', icon: HomeIcon, badge: null }, + { name: 'Workflows', href: '/workflows', icon: CogIcon, badge: null }, + { name: 'Templates', href: '/marketplace', icon: Squares2X2Icon, badge: 'New' }, + { name: 'Integrations', href: '/integrations', icon: LinkIcon, badge: null }, + { name: 'Billing', href: '/billing', icon: CreditCardIcon, badge: null }, + { name: 'Logs', href: '/logs', icon: ChartBarIcon, badge: null }, + { name: 'AI Assistant', href: '/chat', icon: SparklesIcon, badge: 'Beta' }, +]; + +export default function Layout({ children }: LayoutProps) { + const { user, logout } = useAuth(); + const pathname = usePathname(); + + // Admin navigation items + const adminNavigation = user?.role === 'admin' ? [ + { name: 'Admin Panel', href: '/admin', icon: ShieldCheckIcon, badge: 'Admin' } + ] : []; + + // Don't show sidebar on auth pages and landing page + if (!user || pathname === '/' || pathname === '/login' || pathname === '/signup') { + return
{children}
; + } + + return ( +
+ {/* Sidebar */} +
+ {/* Logo */} +
+
+
+ +
+
+

Aimpress

+

AutomationHub

+
+
+
+ + {/* Navigation */} + + + {/* User Profile */} +
+
+
+
+ + {user.first_name?.[0] || user.email[0].toUpperCase()} + +
+
+
+
+

+ {user.first_name} {user.last_name} +

+

{user.email}

+

+ {user.subscription_plan} Plan +

+
+ +
+
+
+ + {/* Main content */} +
+
+ {children} +
+
+ + {/* Floating Action Button */} +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/hooks/useAuth.tsx new file mode 100644 index 0000000..de9c873 --- /dev/null +++ b/frontend/src/hooks/useAuth.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState, useEffect, createContext, useContext } from "react"; +import { User } from "@/types"; +import api from "@/lib/api"; + +interface AuthContextType { + user: User | null; + token: string | null; + login: (email: string, password: string) => Promise; + signup: (email: string, password: string, firstName?: string, lastName?: string) => Promise; + logout: () => void; + loading: boolean; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [token, setToken] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const savedToken = localStorage.getItem("token"); + const savedUser = localStorage.getItem("user"); + + if (savedToken && savedUser) { + setToken(savedToken); + setUser(JSON.parse(savedUser)); + } + setLoading(false); + }, []); + + const login = async (email: string, password: string) => { + try { + const response = await api.post("/auth/login", { email, password }); + const { token, user } = response.data; + + localStorage.setItem("token", token); + localStorage.setItem("user", JSON.stringify(user)); + setToken(token); + setUser(user); + } catch (error: any) { + throw new Error(error.response?.data?.message || "Login failed"); + } + }; + + const signup = async (email: string, password: string, firstName?: string, lastName?: string) => { + try { + const response = await api.post("/auth/signup", { + email, + password, + first_name: firstName, + last_name: lastName, + }); + const { token, user } = response.data; + + localStorage.setItem("token", token); + localStorage.setItem("user", JSON.stringify(user)); + setToken(token); + setUser(user); + } catch (error: any) { + throw new Error(error.response?.data?.message || "Signup failed"); + } + }; + + const logout = () => { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + setToken(null); + setUser(null); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +}; diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..ad817a1 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,32 @@ +import axios from 'axios'; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; + +const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +api.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +api.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + window.location.href = '/login'; + } + return Promise.reject(error); + } +); + +export default api; \ No newline at end of file diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..44b6834 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,75 @@ +export interface User { + id: number; + email: string; + first_name?: string; + last_name?: string; + is_verified: boolean; + role: string; + subscription_plan: string; + subscription_status: string; + created_at: string; + updated_at: string; +} + +export interface Workflow { + id: number; + name: string; + description?: string; + status: string; + trigger_type: string; + is_active: boolean; + created_at: string; + updated_at: string; +} + +export interface Template { + id: number; + name: string; + description?: string; + category: string; + trigger_type: string; + tags: string[]; + is_featured: boolean; + install_count: number; + created_at: string; +} + +export interface Integration { + id: number; + provider: string; + account_name?: string; + account_email?: string; + is_active: boolean; + created_at: string; + updated_at: string; +} + +export interface Webhook { + id: number; + name: string; + slug: string; + trigger_type: string; + url_path: string; + is_active: boolean; + secret_key?: string; + created_at: string; + updated_at: string; +} + +export interface WorkflowExecution { + id: number; + workflow_id: number; + workflow_name: string; + status: string; + trigger_data: any; + error_message?: string; + started_at?: string; + finished_at?: string; + created_at: string; +} + +export interface ChatMessage { + message_type: 'user' | 'assistant'; + content: string; + created_at: string; +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..c133409 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}