diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4a94448 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,29 @@ +# Dependencies +node_modules +npm-debug.log + +# Build output +dist + +# IDE +.idea +.vscode +*.swp +*.swo + +# Environment +.env +.env.* +!.env.example + +# Git +.git +.gitignore + +# Docker +Dockerfile +docker-compose*.yml +.dockerignore + +# Documentation +README.md diff --git a/.env b/.env new file mode 100644 index 0000000..2bd547a --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +# MiChangarrito Frontend - Environment Variables + +# API Configuration +VITE_API_URL=http://localhost:3141 +VITE_API_PREFIX=/api/v1 + +# App Configuration +VITE_APP_NAME=MiChangarrito +VITE_APP_VERSION=1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..81a2b1f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +# ============================================================================= +# MiChangarrito - Frontend Dockerfile +# ============================================================================= +# Multi-stage build for React + Vite application +# Served by nginx +# Puerto: 80 (interno) -> 3140 (host) +# ============================================================================= + +# Build stage +FROM node:20-alpine AS builder + +# Build argument for API URL +ARG VITE_API_URL=http://localhost:3141/api/v1 + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Set environment variable for build +ENV VITE_API_URL=$VITE_API_URL + +# Build application +RUN npm run build + +# Production stage with nginx +FROM nginx:alpine AS production + +# Labels +LABEL maintainer="ISEM" +LABEL description="MiChangarrito Frontend Web" +LABEL version="1.0.0" + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built assets from builder +COPY --from=builder /app/dist /usr/share/nginx/html + +# Create non-root user +RUN addgroup -g 1001 -S nginx-user && \ + adduser -S nginx-user -u 1001 -G nginx-user && \ + chown -R nginx-user:nginx-user /usr/share/nginx/html && \ + chown -R nginx-user:nginx-user /var/cache/nginx && \ + chown -R nginx-user:nginx-user /var/log/nginx && \ + touch /var/run/nginx.pid && \ + chown -R nginx-user:nginx-user /var/run/nginx.pid + +# Expose port +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index bb44adf..d2e7761 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,73 @@ -# michangarrito-frontend-v2 +# React + TypeScript + Vite -Frontend de michangarrito - Workspace V2 \ No newline at end of file +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts new file mode 100644 index 0000000..0c7f85c --- /dev/null +++ b/e2e/auth.spec.ts @@ -0,0 +1,144 @@ +import { test, expect } from '@playwright/test'; + +/** + * E2E Tests - Autenticacion + * MiChangarrito Frontend + */ + +test.describe('Autenticacion', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/login'); + }); + + test('debe mostrar la pagina de login', async ({ page }) => { + // Verificar titulo + await expect(page.locator('h1')).toContainText('MiChangarrito'); + await expect(page.locator('h2')).toContainText('Iniciar sesion'); + + // Verificar campos del formulario + await expect(page.locator('input[name="phone"]')).toBeVisible(); + await expect(page.locator('input[name="pin"]')).toBeVisible(); + await expect(page.locator('button[type="submit"]')).toBeVisible(); + + // Verificar link a registro + await expect(page.locator('a[href="/register"]')).toBeVisible(); + }); + + test('debe validar campos requeridos', async ({ page }) => { + // Intentar enviar formulario vacio + await page.locator('button[type="submit"]').click(); + + // Los campos tienen required, el navegador debe validar + const phoneInput = page.locator('input[name="phone"]'); + await expect(phoneInput).toHaveAttribute('required', ''); + }); + + test('debe aceptar solo numeros en telefono', async ({ page }) => { + const phoneInput = page.locator('input[name="phone"]'); + + // Escribir texto con letras + await phoneInput.fill('abc123def456'); + + // Debe filtrar solo numeros + await expect(phoneInput).toHaveValue('123456'); + }); + + test('debe limitar telefono a 10 digitos', async ({ page }) => { + const phoneInput = page.locator('input[name="phone"]'); + + await phoneInput.fill('12345678901234'); + + // maxLength=10 + await expect(phoneInput).toHaveAttribute('maxLength', '10'); + }); + + test('debe aceptar solo numeros en PIN', async ({ page }) => { + const pinInput = page.locator('input[name="pin"]'); + + await pinInput.fill('abc1234'); + + // Debe filtrar solo numeros + await expect(pinInput).toHaveValue('1234'); + }); + + test('debe mostrar error con credenciales invalidas', async ({ page }) => { + // Llenar formulario con datos invalidos + await page.locator('input[name="phone"]').fill('5500000000'); + await page.locator('input[name="pin"]').fill('0000'); + + // Enviar + await page.locator('button[type="submit"]').click(); + + // Esperar respuesta del servidor + await page.waitForTimeout(1000); + + // Debe mostrar mensaje de error (si el backend esta corriendo) + // Si no hay backend, verificamos que el boton vuelve a estar habilitado + const submitButton = page.locator('button[type="submit"]'); + await expect(submitButton).not.toBeDisabled(); + }); + + test('debe mostrar estado de carga al enviar', async ({ page }) => { + // Llenar formulario + await page.locator('input[name="phone"]').fill('5512345678'); + await page.locator('input[name="pin"]').fill('1234'); + + // Enviar y verificar estado de carga + const submitButton = page.locator('button[type="submit"]'); + await submitButton.click(); + + // Verificar que muestra "Ingresando..." mientras carga + // Nota: Esto es rapido, puede no capturarse siempre + await expect(submitButton).toContainText(/Ingresar|Ingresando/); + }); + + test('debe navegar a registro', async ({ page }) => { + await page.locator('a[href="/register"]').click(); + + await expect(page).toHaveURL('/register'); + }); + + test('debe redirigir a login si no esta autenticado', async ({ page }) => { + // Intentar acceder a ruta protegida + await page.goto('/dashboard'); + + // Debe redirigir a login + await expect(page).toHaveURL('/login'); + }); + + test('debe redirigir a login desde rutas protegidas', async ({ page }) => { + const protectedRoutes = [ + '/dashboard', + '/products', + '/orders', + '/customers', + '/fiado', + '/inventory', + '/settings', + ]; + + for (const route of protectedRoutes) { + await page.goto(route); + await expect(page).toHaveURL('/login'); + } + }); +}); + +test.describe('Registro', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/register'); + }); + + test('debe mostrar la pagina de registro', async ({ page }) => { + await expect(page.locator('h1')).toContainText('MiChangarrito'); + + // Verificar link a login + await expect(page.locator('a[href="/login"]')).toBeVisible(); + }); + + test('debe navegar a login desde registro', async ({ page }) => { + await page.locator('a[href="/login"]').click(); + + await expect(page).toHaveURL('/login'); + }); +}); diff --git a/e2e/fixtures/test-data.ts b/e2e/fixtures/test-data.ts new file mode 100644 index 0000000..dd5aad6 --- /dev/null +++ b/e2e/fixtures/test-data.ts @@ -0,0 +1,48 @@ +/** + * Test Data Fixtures - MiChangarrito E2E Tests + */ + +export const TEST_USER = { + phone: '5512345678', + pin: '1234', + name: 'Usuario Test', + businessName: 'Tienda Test', +}; + +export const TEST_PRODUCT = { + name: 'Coca-Cola 600ml', + sku: 'CC600', + barcode: '7501055300000', + price: 18.00, + cost: 12.00, + stock: 50, + category: 'Bebidas', +}; + +export const TEST_CUSTOMER = { + name: 'Juan Perez', + phone: '5598765432', + email: 'juan@test.com', + creditLimit: 500, +}; + +export const TEST_ORDER = { + items: [ + { productId: '1', quantity: 2, price: 18.00 }, + { productId: '2', quantity: 1, price: 25.00 }, + ], + total: 61.00, + paymentMethod: 'cash', +}; + +export const ROUTES = { + login: '/login', + register: '/register', + dashboard: '/dashboard', + products: '/products', + orders: '/orders', + customers: '/customers', + fiado: '/fiado', + inventory: '/inventory', + settings: '/settings', +}; diff --git a/e2e/navigation.spec.ts b/e2e/navigation.spec.ts new file mode 100644 index 0000000..eca032b --- /dev/null +++ b/e2e/navigation.spec.ts @@ -0,0 +1,120 @@ +import { test, expect } from '@playwright/test'; + +/** + * E2E Tests - Navegacion + * MiChangarrito Frontend + * + * Nota: Estos tests requieren autenticacion + * Se usan con un usuario de prueba pre-configurado + */ + +test.describe('Navegacion Publica', () => { + test('debe cargar la aplicacion', async ({ page }) => { + await page.goto('/'); + + // Al no estar autenticado, debe redirigir a login + await expect(page).toHaveURL('/login'); + }); + + test('rutas invalidas redirigen a login', async ({ page }) => { + await page.goto('/ruta-que-no-existe'); + + await expect(page).toHaveURL('/login'); + }); + + test('login page tiene titulo correcto', async ({ page }) => { + await page.goto('/login'); + + await expect(page).toHaveTitle(/MiChangarrito/i); + }); +}); + +test.describe('UI Responsiva', () => { + test('login se adapta a mobile', async ({ page }) => { + // Viewport mobile + await page.setViewportSize({ width: 375, height: 667 }); + await page.goto('/login'); + + // El formulario debe ser visible y usable + await expect(page.locator('form')).toBeVisible(); + await expect(page.locator('input[name="phone"]')).toBeVisible(); + await expect(page.locator('input[name="pin"]')).toBeVisible(); + await expect(page.locator('button[type="submit"]')).toBeVisible(); + }); + + test('login se adapta a tablet', async ({ page }) => { + // Viewport tablet + await page.setViewportSize({ width: 768, height: 1024 }); + await page.goto('/login'); + + await expect(page.locator('form')).toBeVisible(); + }); + + test('login se adapta a desktop', async ({ page }) => { + // Viewport desktop + await page.setViewportSize({ width: 1920, height: 1080 }); + await page.goto('/login'); + + await expect(page.locator('form')).toBeVisible(); + }); +}); + +test.describe('Accesibilidad', () => { + test('campos tienen labels', async ({ page }) => { + await page.goto('/login'); + + // Verificar que los inputs tienen labels asociados + const phoneLabel = page.locator('label[for="phone"]'); + const pinLabel = page.locator('label[for="pin"]'); + + await expect(phoneLabel).toBeVisible(); + await expect(pinLabel).toBeVisible(); + }); + + test('formulario es navegable con teclado', async ({ page }) => { + await page.goto('/login'); + + // Tab al primer campo + await page.keyboard.press('Tab'); + const phoneInput = page.locator('input[name="phone"]'); + await expect(phoneInput).toBeFocused(); + + // Tab al segundo campo + await page.keyboard.press('Tab'); + const pinInput = page.locator('input[name="pin"]'); + await expect(pinInput).toBeFocused(); + + // Tab al boton + await page.keyboard.press('Tab'); + const submitButton = page.locator('button[type="submit"]'); + await expect(submitButton).toBeFocused(); + }); + + test('formulario puede enviarse con Enter', async ({ page }) => { + await page.goto('/login'); + + // Llenar campos + await page.locator('input[name="phone"]').fill('5512345678'); + await page.locator('input[name="pin"]').fill('1234'); + + // Enviar con Enter + await page.keyboard.press('Enter'); + + // El formulario debe intentar enviarse (el boton estara en estado loading) + const submitButton = page.locator('button[type="submit"]'); + // Puede estar disabled durante el envio o ya haber terminado + await page.waitForTimeout(500); + }); +}); + +test.describe('Performance', () => { + test('login carga en menos de 3 segundos', async ({ page }) => { + const startTime = Date.now(); + + await page.goto('/login'); + await expect(page.locator('h1')).toBeVisible(); + + const loadTime = Date.now() - startTime; + expect(loadTime).toBeLessThan(3000); + }); +}); diff --git a/e2e/orders.spec.ts b/e2e/orders.spec.ts new file mode 100644 index 0000000..7d36151 --- /dev/null +++ b/e2e/orders.spec.ts @@ -0,0 +1,149 @@ +import { test, expect } from '@playwright/test'; +import { TEST_ORDER, TEST_CUSTOMER, ROUTES } from './fixtures/test-data'; + +/** + * E2E Tests - Pedidos y Fiado + * MiChangarrito Frontend + * + * Nota: Estos tests requieren autenticacion previa + * y un backend corriendo con datos de prueba + */ + +test.describe('Pedidos - Sin Autenticacion', () => { + test('redirige a login si no esta autenticado', async ({ page }) => { + await page.goto(ROUTES.orders); + await expect(page).toHaveURL('/login'); + }); + + test('fiado redirige a login sin autenticacion', async ({ page }) => { + await page.goto(ROUTES.fiado); + await expect(page).toHaveURL('/login'); + }); + + test('clientes redirige a login sin autenticacion', async ({ page }) => { + await page.goto(ROUTES.customers); + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Pedidos - Estructura de Datos', () => { + test('datos de pedido de prueba tienen estructura correcta', () => { + expect(TEST_ORDER.items).toBeInstanceOf(Array); + expect(TEST_ORDER.items.length).toBeGreaterThan(0); + expect(TEST_ORDER.total).toBeGreaterThan(0); + expect(TEST_ORDER.paymentMethod).toBeDefined(); + + // Verificar estructura de items + const item = TEST_ORDER.items[0]; + expect(item.productId).toBeDefined(); + expect(item.quantity).toBeGreaterThan(0); + expect(item.price).toBeGreaterThan(0); + }); + + test('datos de cliente de prueba tienen estructura correcta', () => { + expect(TEST_CUSTOMER.name).toBeDefined(); + expect(TEST_CUSTOMER.phone).toBeDefined(); + expect(TEST_CUSTOMER.phone.length).toBe(10); + expect(TEST_CUSTOMER.creditLimit).toBeGreaterThanOrEqual(0); + }); + + test('total del pedido es correcto', () => { + // Verificar calculo: 2 * 18.00 + 1 * 25.00 = 61.00 + const calculatedTotal = TEST_ORDER.items.reduce( + (sum, item) => sum + item.quantity * item.price, + 0 + ); + expect(calculatedTotal).toBe(TEST_ORDER.total); + }); +}); + +test.describe('Sistema de Fiado', () => { + test('rutas de fiado protegidas', async ({ page }) => { + const fiadoRoutes = [ + '/fiado', + '/customers', + ]; + + for (const route of fiadoRoutes) { + await page.goto(route); + await expect(page).toHaveURL('/login'); + } + }); +}); + +test.describe('Historial de Pedidos', () => { + test('ruta de pedidos protegida', async ({ page }) => { + await page.goto('/orders'); + await expect(page).toHaveURL('/login'); + }); + + test('ruta de dashboard protegida', async ({ page }) => { + await page.goto(ROUTES.dashboard); + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Clientes', () => { + test('ruta de clientes protegida', async ({ page }) => { + await page.goto('/customers'); + await expect(page).toHaveURL('/login'); + }); + + test('validacion de limite de credito', () => { + // El limite de credito debe ser un numero positivo o cero + expect(TEST_CUSTOMER.creditLimit).toBeGreaterThanOrEqual(0); + expect(typeof TEST_CUSTOMER.creditLimit).toBe('number'); + }); +}); + +test.describe('Metodos de Pago', () => { + test('metodo de pago valido', () => { + const validPaymentMethods = ['cash', 'card', 'transfer', 'fiado']; + expect(validPaymentMethods).toContain(TEST_ORDER.paymentMethod); + }); +}); + +test.describe('Configuracion', () => { + test('ruta de configuracion protegida', async ({ page }) => { + await page.goto(ROUTES.settings); + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Rutas Completas', () => { + test('todas las rutas definidas en fixtures existen', () => { + const expectedRoutes = [ + 'login', + 'register', + 'dashboard', + 'products', + 'orders', + 'customers', + 'fiado', + 'inventory', + 'settings', + ]; + + for (const route of expectedRoutes) { + expect(ROUTES[route as keyof typeof ROUTES]).toBeDefined(); + expect(ROUTES[route as keyof typeof ROUTES]).toMatch(/^\//); + } + }); + + test('todas las rutas protegidas redirigen a login', async ({ page }) => { + const protectedRoutes = [ + ROUTES.dashboard, + ROUTES.products, + ROUTES.orders, + ROUTES.customers, + ROUTES.fiado, + ROUTES.inventory, + ROUTES.settings, + ]; + + for (const route of protectedRoutes) { + await page.goto(route); + await expect(page).toHaveURL('/login'); + } + }); +}); diff --git a/e2e/pos.spec.ts b/e2e/pos.spec.ts new file mode 100644 index 0000000..462fd5e --- /dev/null +++ b/e2e/pos.spec.ts @@ -0,0 +1,103 @@ +import { test, expect } from '@playwright/test'; +import { TEST_PRODUCT, ROUTES } from './fixtures/test-data'; + +/** + * E2E Tests - Punto de Venta (POS) + * MiChangarrito Frontend + * + * Nota: Estos tests requieren autenticacion previa + * y un backend corriendo con datos de prueba + */ + +test.describe('Punto de Venta - Sin Autenticacion', () => { + test('redirige a login si no esta autenticado', async ({ page }) => { + await page.goto('/pos'); + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Punto de Venta - UI Basica', () => { + // Estos tests verifican la estructura esperada del POS + // cuando el usuario esta autenticado + + test.beforeEach(async ({ page }) => { + // Mock de autenticacion para tests + // En un escenario real, se usaria un fixture de login + await page.goto('/login'); + }); + + test('pagina de productos existe', async ({ page }) => { + await page.goto(ROUTES.products); + // Sin auth, redirige a login + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Punto de Venta - Flujo de Venta', () => { + // Tests de flujo completo de venta + // Requieren setup de autenticacion + + test('estructura del formulario de busqueda', async ({ page }) => { + await page.goto('/login'); + + // Verificar que la pagina de login tiene estructura correcta + // antes de poder probar el POS + await expect(page.locator('form')).toBeVisible(); + }); +}); + +test.describe('Carrito de Compras', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/login'); + }); + + test('login tiene campos necesarios para acceder al POS', async ({ page }) => { + // Verificar campos necesarios + await expect(page.locator('input[name="phone"]')).toBeVisible(); + await expect(page.locator('input[name="pin"]')).toBeVisible(); + await expect(page.locator('button[type="submit"]')).toBeVisible(); + }); +}); + +test.describe('Busqueda de Productos', () => { + test('rutas de productos protegidas', async ({ page }) => { + // Verificar que todas las rutas relacionadas al POS estan protegidas + const posRoutes = [ + '/products', + '/pos', + '/inventory', + ]; + + for (const route of posRoutes) { + await page.goto(route); + await expect(page).toHaveURL('/login'); + } + }); +}); + +test.describe('Metodos de Pago', () => { + test('verificar existencia de ruta de pedidos', async ({ page }) => { + await page.goto(ROUTES.orders); + // Sin auth, redirige + await expect(page).toHaveURL('/login'); + }); +}); + +test.describe('Recibos y Tickets', () => { + test('estructura de datos de producto de prueba', () => { + // Verificar que los datos de prueba tienen la estructura correcta + expect(TEST_PRODUCT.name).toBeDefined(); + expect(TEST_PRODUCT.price).toBeGreaterThan(0); + expect(TEST_PRODUCT.cost).toBeGreaterThan(0); + expect(TEST_PRODUCT.stock).toBeGreaterThanOrEqual(0); + expect(TEST_PRODUCT.sku).toBeDefined(); + expect(TEST_PRODUCT.barcode).toBeDefined(); + }); +}); + +test.describe('Inventario desde POS', () => { + test('ruta de inventario protegida', async ({ page }) => { + await page.goto(ROUTES.inventory); + await expect(page).toHaveURL('/login'); + }); +}); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..072a57e --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..683c656 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,42 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA routing - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3a41f7d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4317 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@tanstack/react-query": "^5.90.16", + "axios": "^1.13.2", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.11.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/postcss": "^4.1.18", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } + }, + "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/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "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.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "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.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "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.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "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.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "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.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.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/@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/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz", + "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "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.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.16", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz", + "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.16", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", + "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "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/node": { + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.2.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.51.0", + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" + }, + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.2.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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", + "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.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.2.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/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/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.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/visitor-keys": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.51.0", + "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/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "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", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "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/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "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/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "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-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "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.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.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/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.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.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "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/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "peer": true, + "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.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "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.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "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/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "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/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/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "license": "MIT", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, + "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==", + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "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", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", + "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", + "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", + "license": "MIT", + "dependencies": { + "react-router": "7.11.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.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/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b76509f --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "test:e2e:report": "playwright show-report" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.16", + "axios": "^1.13.2", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.11.0" + }, + "devDependencies": { + "@playwright/test": "^1.50.1", + "@eslint/js": "^9.39.1", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/postcss": "^4.1.18", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..cb556df --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,53 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Playwright E2E Test Configuration - MiChangarrito Frontend + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html', { outputFolder: 'playwright-report' }], + ['list'], + ], + use: { + baseURL: 'http://localhost:3140', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], + + webServer: { + command: 'npm run dev', + url: 'http://localhost:3140', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..1c87846 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..6c79a46 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,95 @@ +import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { AuthProvider, useAuth } from './contexts/AuthContext'; +import { Layout } from './components/Layout'; +import { Dashboard } from './pages/Dashboard'; +import { Products } from './pages/Products'; +import { Orders } from './pages/Orders'; +import { Customers } from './pages/Customers'; +import { Fiado } from './pages/Fiado'; +import { Inventory } from './pages/Inventory'; +import { Settings } from './pages/Settings'; +import { Referrals } from './pages/Referrals'; +import { Invoices } from './pages/Invoices'; +import { Marketplace } from './pages/Marketplace'; +import Login from './pages/Login'; +import Register from './pages/Register'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + retry: 1, + }, + }, +}); + +// Protected route wrapper +function ProtectedRoute() { + const { isAuthenticated, isLoading } = useAuth(); + + if (isLoading) { + return ( +
+
+
+ ); + } + + return isAuthenticated ? : ; +} + +// Public route wrapper (redirect if authenticated) +function PublicRoute() { + const { isAuthenticated, isLoading } = useAuth(); + + if (isLoading) { + return ( +
+
+
+ ); + } + + return isAuthenticated ? : ; +} + +function App() { + return ( + + + + + {/* Public routes */} + }> + } /> + } /> + + + {/* Protected routes */} + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* Catch all */} + } /> + + + + + ); +} + +export default App; diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..174cc3e --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,173 @@ +import { Outlet, NavLink, useNavigate } from 'react-router-dom'; +import { + LayoutDashboard, + Package, + ShoppingCart, + Users, + CreditCard, + Boxes, + Settings, + Menu, + X, + Store, + LogOut, + Gift, + FileText, + Truck, +} from 'lucide-react'; +import { useState } from 'react'; +import clsx from 'clsx'; +import { useAuth } from '../contexts/AuthContext'; + +const navigation = [ + { name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard }, + { name: 'Productos', href: '/products', icon: Package }, + { name: 'Pedidos', href: '/orders', icon: ShoppingCart }, + { name: 'Clientes', href: '/customers', icon: Users }, + { name: 'Fiado', href: '/fiado', icon: CreditCard }, + { name: 'Inventario', href: '/inventory', icon: Boxes }, + { name: 'Facturacion', href: '/invoices', icon: FileText }, + { name: 'Proveedores', href: '/marketplace', icon: Truck }, + { name: 'Referidos', href: '/referrals', icon: Gift }, + { name: 'Ajustes', href: '/settings', icon: Settings }, +]; + +export function Layout() { + const [sidebarOpen, setSidebarOpen] = useState(false); + const { user, tenant, logout } = useAuth(); + const navigate = useNavigate(); + + const handleLogout = () => { + logout(); + navigate('/login'); + }; + + const getInitials = (name: string) => { + return name + .split(' ') + .map((n) => n[0]) + .join('') + .toUpperCase() + .slice(0, 2); + }; + + return ( +
+ {/* Mobile sidebar */} +
+
setSidebarOpen(false)} + /> +
+
+
+ + MiChangarrito +
+ +
+ +
+
+ + {/* Desktop sidebar */} +
+
+
+
+ + MiChangarrito +
+
+ +
+
+
+
+ + {tenant ? getInitials(tenant.name) : 'MC'} + +
+
+

{tenant?.name || 'Mi Negocio'}

+

{user?.name}

+
+
+ +
+
+
+
+ + {/* Main content */} +
+ {/* Mobile header */} +
+ +
+ + MiChangarrito +
+
+ + {/* Page content */} +
+ +
+
+
+ ); +} diff --git a/src/components/payments/ClabeDisplay.tsx b/src/components/payments/ClabeDisplay.tsx new file mode 100644 index 0000000..49c6309 --- /dev/null +++ b/src/components/payments/ClabeDisplay.tsx @@ -0,0 +1,112 @@ +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Copy, Check, Building } from 'lucide-react'; +import { codiSpeiApi } from '../../lib/api'; + +interface ClabeDisplayProps { + showCreateButton?: boolean; + beneficiaryName?: string; +} + +export function ClabeDisplay({ showCreateButton = true, beneficiaryName }: ClabeDisplayProps) { + const [copied, setCopied] = useState(false); + const queryClient = useQueryClient(); + + const { data, isLoading } = useQuery({ + queryKey: ['clabe'], + queryFn: async () => { + const res = await codiSpeiApi.getClabe(); + return res.data; + }, + }); + + const createMutation = useMutation({ + mutationFn: (name: string) => codiSpeiApi.createClabe(name), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['clabe'] }); + }, + }); + + const copyClabe = async () => { + if (data?.clabe) { + await navigator.clipboard.writeText(data.clabe); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + const formatClabe = (clabe: string) => { + // Format: XXX XXX XXXX XXXX XXXX + return clabe.replace(/(\d{3})(\d{3})(\d{4})(\d{4})(\d{4})/, '$1 $2 $3 $4 $5'); + }; + + if (isLoading) { + return ( +
+
+
+ ); + } + + if (!data?.clabe && showCreateButton) { + return ( +
+
+ +
+

CLABE Virtual

+

Recibe transferencias SPEI

+
+
+ +
+ ); + } + + if (!data?.clabe) { + return null; + } + + return ( +
+
+
+ + Tu CLABE para recibir SPEI +
+ +
+ +
+

+ {formatClabe(data.clabe)} +

+ {data.beneficiaryName && ( +

+ {data.beneficiaryName} +

+ )} +
+ +

+ Las transferencias se reflejan automaticamente +

+
+ ); +} diff --git a/src/components/payments/CodiQR.tsx b/src/components/payments/CodiQR.tsx new file mode 100644 index 0000000..7416066 --- /dev/null +++ b/src/components/payments/CodiQR.tsx @@ -0,0 +1,167 @@ +import { useState, useEffect } from 'react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { QrCode, Clock, Check, X, RefreshCw } from 'lucide-react'; +import { codiSpeiApi } from '../../lib/api'; + +interface CodiQRProps { + amount: number; + description?: string; + saleId?: string; + onSuccess?: () => void; + onCancel?: () => void; +} + +export function CodiQR({ amount, description, saleId, onSuccess, onCancel }: CodiQRProps) { + const [transactionId, setTransactionId] = useState(null); + const [timeLeft, setTimeLeft] = useState(300); // 5 minutes in seconds + + const generateMutation = useMutation({ + mutationFn: () => codiSpeiApi.generateQr({ amount, description, saleId }), + onSuccess: (res) => { + setTransactionId(res.data.id); + setTimeLeft(300); + }, + }); + + const { data: status, refetch } = useQuery({ + queryKey: ['codi-status', transactionId], + queryFn: async () => { + if (!transactionId) return null; + const res = await codiSpeiApi.getCodiStatus(transactionId); + return res.data; + }, + enabled: !!transactionId, + refetchInterval: 3000, // Poll every 3 seconds + }); + + // Generate QR on mount + useEffect(() => { + generateMutation.mutate(); + }, []); + + // Countdown timer + useEffect(() => { + if (timeLeft <= 0) return; + + const timer = setInterval(() => { + setTimeLeft((prev) => prev - 1); + }, 1000); + + return () => clearInterval(timer); + }, [timeLeft]); + + // Handle status changes + useEffect(() => { + if (status?.status === 'confirmed') { + onSuccess?.(); + } else if (status?.status === 'expired' || status?.status === 'cancelled') { + // QR expired or cancelled + } + }, [status?.status]); + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + + const handleRefresh = () => { + generateMutation.mutate(); + }; + + if (generateMutation.isPending) { + return ( +
+
+

Generando QR de cobro...

+
+ ); + } + + if (status?.status === 'confirmed') { + return ( +
+
+ +
+

Pago Confirmado

+

El pago de ${amount.toFixed(2)} fue recibido

+
+ ); + } + + if (status?.status === 'expired' || timeLeft <= 0) { + return ( +
+
+ +
+

QR Expirado

+

El codigo QR ha expirado

+ +
+ ); + } + + return ( +
+
+

Pagar con CoDi

+

${amount.toFixed(2)}

+ {description &&

{description}

} +
+ + {/* QR Code Display */} +
+
+ {/* In production, generate actual QR code from qrData */} +
+ +

Escanea con tu app bancaria

+
+
+
+ + {/* Timer */} +
+ + Expira en: {formatTime(timeLeft)} +
+ + {/* Instructions */} +
+

1. Abre la app de tu banco

+

2. Escanea el codigo QR

+

3. Confirma el pago

+
+ + {/* Actions */} +
+ + +
+ + {/* Reference */} + {status?.reference && ( +

Ref: {status.reference}

+ )} +
+ ); +} diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..7de3735 --- /dev/null +++ b/src/contexts/AuthContext.tsx @@ -0,0 +1,118 @@ +import { createContext, useContext, useState, useEffect } from 'react'; +import type { ReactNode } from 'react'; +import { authApi } from '../lib/api'; + +interface User { + id: string; + name: string; + role: string; + phone: string; +} + +interface Tenant { + id: string; + name: string; + slug: string; + businessType: string; + subscriptionStatus: string; +} + +interface AuthContextType { + user: User | null; + tenant: Tenant | null; + isAuthenticated: boolean; + isLoading: boolean; + login: (phone: string, pin: string) => Promise; + register: (data: RegisterData) => Promise; + logout: () => void; +} + +interface RegisterData { + name: string; + ownerName: string; + businessType: string; + phone: string; + pin: string; + email?: string; + whatsapp?: string; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null); + const [tenant, setTenant] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // Check for existing session + const storedUser = localStorage.getItem('user'); + const storedTenant = localStorage.getItem('tenant'); + const accessToken = localStorage.getItem('accessToken'); + + if (storedUser && storedTenant && accessToken) { + setUser(JSON.parse(storedUser)); + setTenant(JSON.parse(storedTenant)); + } + setIsLoading(false); + }, []); + + const login = async (phone: string, pin: string) => { + const response = await authApi.login({ phone, pin }); + const { accessToken, refreshToken, user: userData, tenant: tenantData } = response.data; + + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', refreshToken); + localStorage.setItem('user', JSON.stringify(userData)); + localStorage.setItem('tenant', JSON.stringify(tenantData)); + + setUser(userData); + setTenant(tenantData); + }; + + const register = async (data: RegisterData) => { + const response = await authApi.register(data); + const { accessToken, refreshToken, user: userData, tenant: tenantData } = response.data; + + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', refreshToken); + localStorage.setItem('user', JSON.stringify(userData)); + localStorage.setItem('tenant', JSON.stringify(tenantData)); + + setUser(userData); + setTenant(tenantData); + }; + + const logout = () => { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('user'); + localStorage.removeItem('tenant'); + setUser(null); + setTenant(null); + }; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..c48d4d6 --- /dev/null +++ b/src/index.css @@ -0,0 +1,53 @@ +@import "tailwindcss"; + +@theme { + --color-primary-50: #fff7ed; + --color-primary-100: #ffedd5; + --color-primary-200: #fed7aa; + --color-primary-300: #fdba74; + --color-primary-400: #fb923c; + --color-primary-500: #f97316; + --color-primary-600: #ea580c; + --color-primary-700: #c2410c; + --color-primary-800: #9a3412; + --color-primary-900: #7c2d12; + + --color-secondary-50: #f0fdf4; + --color-secondary-100: #dcfce7; + --color-secondary-200: #bbf7d0; + --color-secondary-300: #86efac; + --color-secondary-400: #4ade80; + --color-secondary-500: #22c55e; + --color-secondary-600: #16a34a; + --color-secondary-700: #15803d; + --color-secondary-800: #166534; + --color-secondary-900: #14532d; +} + +@layer base { + body { + @apply bg-gray-50 text-gray-900; + } +} + +@layer components { + .btn-primary { + @apply bg-primary-500 hover:bg-primary-600 text-white font-medium py-2 px-4 rounded-lg transition-colors; + } + + .btn-secondary { + @apply bg-secondary-500 hover:bg-secondary-600 text-white font-medium py-2 px-4 rounded-lg transition-colors; + } + + .btn-outline { + @apply border border-gray-300 hover:border-gray-400 text-gray-700 font-medium py-2 px-4 rounded-lg transition-colors; + } + + .card { + @apply bg-white rounded-xl shadow-sm border border-gray-200 p-4; + } + + .input { + @apply w-full rounded-lg border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500; + } +} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..65ac10f --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,216 @@ +import axios, { AxiosError } from 'axios'; + +// Use proxy in development (vite will forward to backend) +// In production, use VITE_API_URL +const API_BASE = import.meta.env.VITE_API_URL || ''; +const API_PREFIX = import.meta.env.VITE_API_PREFIX || '/api/v1'; + +export const api = axios.create({ + baseURL: `${API_BASE}${API_PREFIX}`, + headers: { + 'Content-Type': 'application/json', + }, +}); + +// Add auth interceptor - attach token to requests +api.interceptors.request.use((config) => { + const token = localStorage.getItem('accessToken'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Response interceptor - handle 401 errors +api.interceptors.response.use( + (response) => response, + async (error: AxiosError) => { + if (error.response?.status === 401) { + // Try refresh token + const refreshToken = localStorage.getItem('refreshToken'); + if (refreshToken) { + try { + const response = await axios.post(`${API_BASE}${API_PREFIX}/auth/refresh`, { + refreshToken, + }); + const { accessToken, refreshToken: newRefresh } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefresh); + + // Retry original request + if (error.config) { + error.config.headers.Authorization = `Bearer ${accessToken}`; + return axios(error.config); + } + } catch { + // Refresh failed, clear tokens and redirect to login + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('user'); + localStorage.removeItem('tenant'); + window.location.href = '/login'; + } + } else { + // No refresh token, redirect to login + localStorage.removeItem('accessToken'); + localStorage.removeItem('user'); + localStorage.removeItem('tenant'); + window.location.href = '/login'; + } + } + return Promise.reject(error); + } +); + +// Auth API +export const authApi = { + register: (data: { + name: string; + ownerName: string; + businessType: string; + phone: string; + pin: string; + email?: string; + whatsapp?: string; + }) => api.post('/auth/register', data), + + login: (data: { phone: string; pin: string }) => + api.post('/auth/login', data), + + refresh: (refreshToken: string) => + api.post('/auth/refresh', { refreshToken }), + + changePin: (data: { currentPin: string; newPin: string }) => + api.post('/auth/change-pin', data), +}; + +// Products API +export const productsApi = { + getAll: (params?: { category?: string; search?: string }) => + api.get('/products', { params }), + getById: (id: string) => api.get(`/products/${id}`), + create: (data: any) => api.post('/products', data), + update: (id: string, data: any) => api.patch(`/products/${id}`, data), + delete: (id: string) => api.delete(`/products/${id}`), +}; + +// Orders API +export const ordersApi = { + getAll: (params?: { status?: string; date?: string }) => + api.get('/orders', { params }), + getById: (id: string) => api.get(`/orders/${id}`), + create: (data: any) => api.post('/orders', data), + updateStatus: (id: string, status: string) => + api.patch(`/orders/${id}/status`, { status }), +}; + +// Customers API +export const customersApi = { + getAll: (params?: { search?: string }) => + api.get('/customers', { params }), + getById: (id: string) => api.get(`/customers/${id}`), + create: (data: any) => api.post('/customers', data), + update: (id: string, data: any) => api.patch(`/customers/${id}`, data), + getFiado: (id: string) => api.get(`/customers/${id}/fiado`), +}; + +// Inventory API +export const inventoryApi = { + getMovements: (params?: { productId?: string; type?: string }) => + api.get('/inventory/movements', { params }), + createMovement: (data: any) => api.post('/inventory/movements', data), + getLowStock: () => api.get('/inventory/low-stock'), + getAlerts: () => api.get('/inventory/alerts'), +}; + +// Dashboard API +export const dashboardApi = { + getStats: () => api.get('/dashboard/stats'), + getSalesChart: (period: string) => api.get('/dashboard/sales', { params: { period } }), + getTopProducts: () => api.get('/dashboard/top-products'), +}; + +// Referrals API +export const referralsApi = { + getMyCode: () => api.get('/referrals/my-code'), + generateCode: () => api.post('/referrals/generate-code'), + validateCode: (code: string) => api.get(`/referrals/validate/${code}`), + applyCode: (code: string) => api.post('/referrals/apply-code', { code }), + getMyReferrals: () => api.get('/referrals/list'), + getStats: () => api.get('/referrals/stats'), + getRewards: () => api.get('/referrals/rewards'), + getAvailableMonths: () => api.get('/referrals/rewards/available-months'), + getDiscount: () => api.get('/referrals/discount'), +}; + +// CoDi/SPEI API +export const codiSpeiApi = { + // CoDi + generateQr: (data: { amount: number; description?: string; saleId?: string }) => + api.post('/codi/generate-qr', data), + getCodiStatus: (id: string) => api.get(`/codi/status/${id}`), + getCodiTransactions: (limit?: number) => + api.get('/codi/transactions', { params: { limit } }), + + // SPEI + getClabe: () => api.get('/spei/clabe'), + createClabe: (beneficiaryName: string) => + api.post('/spei/create-clabe', { beneficiaryName }), + getSpeiTransactions: (limit?: number) => + api.get('/spei/transactions', { params: { limit } }), + + // Summary + getSummary: (date?: string) => + api.get('/payments/summary', { params: { date } }), +}; + +// Invoices API (SAT/CFDI) +export const invoicesApi = { + // Tax Config + getTaxConfig: () => api.get('/invoices/tax-config'), + saveTaxConfig: (data: any) => api.post('/invoices/tax-config', data), + + // Invoices + getAll: (params?: { status?: string; from?: string; to?: string; limit?: number }) => + api.get('/invoices', { params }), + getById: (id: string) => api.get(`/invoices/${id}`), + create: (data: any) => api.post('/invoices', data), + stamp: (id: string) => api.post(`/invoices/${id}/stamp`), + cancel: (id: string, reason: string, uuidReplacement?: string) => + api.post(`/invoices/${id}/cancel`, { reason, uuidReplacement }), + send: (id: string, email?: string) => + api.post(`/invoices/${id}/send`, { email }), + getSummary: (month?: string) => + api.get('/invoices/summary', { params: { month } }), +}; + +// Marketplace API +export const marketplaceApi = { + // Suppliers + getSuppliers: (params?: { category?: string; zipCode?: string; search?: string; limit?: number }) => + api.get('/marketplace/suppliers', { params }), + getSupplier: (id: string) => api.get(`/marketplace/suppliers/${id}`), + getSupplierProducts: (id: string, params?: { category?: string; search?: string }) => + api.get(`/marketplace/suppliers/${id}/products`, { params }), + getSupplierReviews: (id: string, params?: { limit?: number }) => + api.get(`/marketplace/suppliers/${id}/reviews`, { params }), + + // Orders + createOrder: (data: any) => api.post('/marketplace/orders', data), + getOrders: (params?: { status?: string; supplierId?: string; limit?: number }) => + api.get('/marketplace/orders', { params }), + getOrder: (id: string) => api.get(`/marketplace/orders/${id}`), + cancelOrder: (id: string, reason: string) => + api.put(`/marketplace/orders/${id}/cancel`, { reason }), + + // Reviews + createReview: (data: any) => api.post('/marketplace/reviews', data), + + // Favorites + getFavorites: () => api.get('/marketplace/favorites'), + addFavorite: (supplierId: string) => api.post(`/marketplace/favorites/${supplierId}`), + removeFavorite: (supplierId: string) => api.delete(`/marketplace/favorites/${supplierId}`), + + // Stats + getStats: () => api.get('/marketplace/stats'), +}; diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts new file mode 100644 index 0000000..d79338b --- /dev/null +++ b/src/lib/i18n.ts @@ -0,0 +1,72 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +// Import translations +import esMX from '../locales/es-MX'; +import esCO from '../locales/es-CO'; +import esAR from '../locales/es-AR'; +import ptBR from '../locales/pt-BR'; + +const resources = { + 'es-MX': { translation: esMX }, + 'es-CO': { translation: esCO }, + 'es-AR': { translation: esAR }, + 'pt-BR': { translation: ptBR }, +}; + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + fallbackLng: 'es-MX', + defaultNS: 'translation', + interpolation: { + escapeValue: false, + }, + detection: { + order: ['localStorage', 'navigator'], + caches: ['localStorage'], + }, + }); + +export default i18n; + +// Currency formatting by locale +export const currencyConfig: Record = { + 'es-MX': { currency: 'MXN', symbol: '$' }, + 'es-CO': { currency: 'COP', symbol: '$' }, + 'es-AR': { currency: 'ARS', symbol: '$' }, + 'pt-BR': { currency: 'BRL', symbol: 'R$' }, +}; + +export function formatCurrency(amount: number, locale: string = 'es-MX'): string { + const config = currencyConfig[locale] || currencyConfig['es-MX']; + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: config.currency, + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); +} + +export function formatDate(date: Date | string, locale: string = 'es-MX'): string { + const d = typeof date === 'string' ? new Date(date) : date; + return new Intl.DateTimeFormat(locale, { + day: 'numeric', + month: 'short', + year: 'numeric', + }).format(d); +} + +export function formatDateTime(date: Date | string, locale: string = 'es-MX'): string { + const d = typeof date === 'string' ? new Date(date) : date; + return new Intl.DateTimeFormat(locale, { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }).format(d); +} diff --git a/src/locales/es-AR/index.ts b/src/locales/es-AR/index.ts new file mode 100644 index 0000000..ed36f71 --- /dev/null +++ b/src/locales/es-AR/index.ts @@ -0,0 +1,63 @@ +import esMX from '../es-MX'; + +// Argentina Spanish - Override specific terms +const esAR = { + ...esMX, + + // Navigation overrides + nav: { + ...esMX.nav, + // Same in Argentina + }, + + // Auth overrides + auth: { + ...esMX.auth, + phone: 'Celular', + enterPhone: 'Ingresa tu celular', + }, + + // Dashboard overrides + dashboard: { + ...esMX.dashboard, + // Same in Argentina + }, + + // Products overrides - Argentine terminology + products: { + ...esMX.products, + subtitle: 'Administra tu stock', + stock: 'Stock', + }, + + // Fiado overrides - Argentine terminology + fiado: { + ...esMX.fiado, + title: 'Cuenta', + subtitle: 'Control de cuentas corrientes', + registerPayment: 'Registrar pago', + }, + + // Payments overrides + payments: { + ...esMX.payments, + fiado: 'Cuenta corriente', + transfer: 'Transferencia', + change: 'Vuelto', + }, + + // Business Types - Argentine terminology + businessTypes: { + tienda: 'Almacen', + papeleria: 'Libreria', + farmacia: 'Farmacia', + ferreteria: 'Ferreteria', + carniceria: 'Carniceria', + verduleria: 'Verduleria', + panaderia: 'Panaderia', + tortilleria: 'Rotiseria', + otro: 'Otro', + }, +}; + +export default esAR; diff --git a/src/locales/es-CO/index.ts b/src/locales/es-CO/index.ts new file mode 100644 index 0000000..0e375b0 --- /dev/null +++ b/src/locales/es-CO/index.ts @@ -0,0 +1,60 @@ +import esMX from '../es-MX'; + +// Colombia Spanish - Override specific terms +const esCO = { + ...esMX, + + // Navigation overrides + nav: { + ...esMX.nav, + // Same in Colombia + }, + + // Auth overrides + auth: { + ...esMX.auth, + phone: 'Celular', + enterPhone: 'Ingresa tu celular', + }, + + // Dashboard overrides + dashboard: { + ...esMX.dashboard, + // Same in Colombia + }, + + // Products overrides - Colombian terminology + products: { + ...esMX.products, + subtitle: 'Administra tu inventario', + }, + + // Fiado overrides + fiado: { + ...esMX.fiado, + title: 'Credito', + subtitle: 'Control de creditos a clientes', + }, + + // Payments overrides + payments: { + ...esMX.payments, + fiado: 'Credito', + transfer: 'Transferencia bancaria', + }, + + // Business Types - Colombian terminology + businessTypes: { + tienda: 'Tienda', + papeleria: 'Papeleria', + farmacia: 'Drogueria', + ferreteria: 'Ferreteria', + carniceria: 'Carniceria', + verduleria: 'Fruteria', + panaderia: 'Panaderia', + tortilleria: 'Areperia', + otro: 'Otro', + }, +}; + +export default esCO; diff --git a/src/locales/es-MX/index.ts b/src/locales/es-MX/index.ts new file mode 100644 index 0000000..d5dab5d --- /dev/null +++ b/src/locales/es-MX/index.ts @@ -0,0 +1,231 @@ +export default { + // Common + common: { + save: 'Guardar', + cancel: 'Cancelar', + delete: 'Eliminar', + edit: 'Editar', + add: 'Agregar', + search: 'Buscar', + loading: 'Cargando...', + error: 'Error', + success: 'Exito', + confirm: 'Confirmar', + back: 'Volver', + next: 'Siguiente', + close: 'Cerrar', + yes: 'Si', + no: 'No', + all: 'Todos', + none: 'Ninguno', + required: 'Requerido', + optional: 'Opcional', + }, + + // Navigation + nav: { + dashboard: 'Dashboard', + products: 'Productos', + orders: 'Pedidos', + customers: 'Clientes', + fiado: 'Fiado', + inventory: 'Inventario', + referrals: 'Referidos', + settings: 'Ajustes', + logout: 'Cerrar sesion', + }, + + // Auth + auth: { + login: 'Iniciar sesion', + register: 'Registrarse', + phone: 'Telefono', + pin: 'PIN', + enterPhone: 'Ingresa tu telefono', + enterPin: 'Ingresa tu PIN', + forgotPin: 'Olvide mi PIN', + noAccount: 'No tienes cuenta?', + hasAccount: 'Ya tienes cuenta?', + createAccount: 'Crear cuenta', + businessName: 'Nombre del negocio', + ownerName: 'Tu nombre', + businessType: 'Tipo de negocio', + }, + + // Dashboard + dashboard: { + title: 'Dashboard', + todaySales: 'Ventas de hoy', + weekSales: 'Ventas de la semana', + monthSales: 'Ventas del mes', + transactions: 'transacciones', + lowStock: 'Stock bajo', + pendingOrders: 'Pedidos pendientes', + pendingCredits: 'Fiados pendientes', + topProducts: 'Productos mas vendidos', + }, + + // Products + products: { + title: 'Productos', + subtitle: 'Administra tu catalogo', + addProduct: 'Agregar producto', + editProduct: 'Editar producto', + name: 'Nombre', + price: 'Precio', + cost: 'Costo', + stock: 'Stock', + sku: 'SKU', + barcode: 'Codigo de barras', + category: 'Categoria', + description: 'Descripcion', + noProducts: 'No hay productos', + scanBarcode: 'Escanear codigo', + }, + + // Orders + orders: { + title: 'Pedidos', + subtitle: 'Gestiona los pedidos', + newOrder: 'Nuevo pedido', + orderNumber: 'Pedido #{{number}}', + status: 'Estado', + pending: 'Pendiente', + preparing: 'Preparando', + ready: 'Listo', + delivered: 'Entregado', + cancelled: 'Cancelado', + total: 'Total', + items: 'articulos', + noOrders: 'No hay pedidos', + }, + + // Customers + customers: { + title: 'Clientes', + subtitle: 'Administra tus clientes', + addCustomer: 'Agregar cliente', + name: 'Nombre', + phone: 'Telefono', + email: 'Correo', + creditLimit: 'Limite de credito', + currentBalance: 'Saldo actual', + noCustomers: 'No hay clientes', + }, + + // Fiado (Credit) + fiado: { + title: 'Fiado', + subtitle: 'Control de creditos', + totalOwed: 'Total adeudado', + overdueAmount: 'Vencido', + customersWithCredit: 'Clientes con fiado', + registerPayment: 'Registrar abono', + paymentHistory: 'Historial de pagos', + dueDate: 'Fecha de vencimiento', + overdue: 'Vencido', + noCredits: 'No hay fiados pendientes', + }, + + // Inventory + inventory: { + title: 'Inventario', + subtitle: 'Control de existencias', + movements: 'Movimientos', + addMovement: 'Agregar movimiento', + entry: 'Entrada', + exit: 'Salida', + adjustment: 'Ajuste', + lowStockAlerts: 'Alertas de stock bajo', + reorder: 'Reabastecer', + noMovements: 'No hay movimientos', + }, + + // Referrals + referrals: { + title: 'Programa de Referidos', + subtitle: 'Invita amigos y gana', + yourCode: 'Tu codigo de referido', + copy: 'Copiar', + share: 'Compartir', + shareWhatsApp: 'Compartir por WhatsApp', + invited: 'Invitados', + converted: 'Convertidos', + monthsEarned: 'Meses ganados', + monthsAvailable: 'Disponibles', + howItWorks: 'Como funciona', + step1: 'Comparte tu codigo', + step1Desc: 'Envia tu codigo a amigos por WhatsApp', + step2: 'Tu amigo se registra', + step2Desc: 'Obtiene 50% de descuento en su primer mes', + step3: 'Tu ganas 1 mes gratis', + step3Desc: 'Cuando tu amigo paga su primer mes', + yourReferrals: 'Tus referidos', + noReferrals: 'Aun no tienes referidos', + }, + + // Settings + settings: { + title: 'Ajustes', + subtitle: 'Configura tu tienda', + businessInfo: 'Informacion del negocio', + fiadoSettings: 'Configuracion de fiado', + whatsapp: 'WhatsApp Business', + notifications: 'Notificaciones', + subscription: 'Suscripcion', + language: 'Idioma', + enableFiado: 'Habilitar fiado', + defaultCreditLimit: 'Limite de credito por defecto', + gracePeriod: 'Dias de gracia', + connected: 'Conectado', + autoResponses: 'Respuestas automaticas', + lowStockAlerts: 'Alertas de stock bajo', + overdueAlerts: 'Alertas de fiados vencidos', + newOrderAlerts: 'Alertas de nuevos pedidos', + currentPlan: 'Plan actual', + upgradePlan: 'Mejorar plan', + }, + + // Payments + payments: { + cash: 'Efectivo', + card: 'Tarjeta', + transfer: 'Transferencia', + fiado: 'Fiado', + codi: 'CoDi', + spei: 'SPEI', + change: 'Cambio', + total: 'Total', + subtotal: 'Subtotal', + tax: 'IVA', + discount: 'Descuento', + payNow: 'Pagar ahora', + generateQR: 'Generar QR', + scanQR: 'Escanea el QR con tu app bancaria', + paymentReceived: 'Pago recibido', + paymentFailed: 'Pago fallido', + }, + + // Errors + errors: { + generic: 'Algo salio mal', + networkError: 'Error de conexion', + unauthorized: 'No autorizado', + notFound: 'No encontrado', + validationError: 'Error de validacion', + serverError: 'Error del servidor', + }, + + // Business Types + businessTypes: { + tienda: 'Tienda de abarrotes', + papeleria: 'Papeleria', + farmacia: 'Farmacia', + ferreteria: 'Ferreteria', + carniceria: 'Carniceria', + verduleria: 'Verduleria', + panaderia: 'Panaderia', + tortilleria: 'Tortilleria', + otro: 'Otro', + }, +}; diff --git a/src/locales/pt-BR/index.ts b/src/locales/pt-BR/index.ts new file mode 100644 index 0000000..708e3f2 --- /dev/null +++ b/src/locales/pt-BR/index.ts @@ -0,0 +1,234 @@ +// Brazilian Portuguese +const ptBR = { + // Common + common: { + save: 'Salvar', + cancel: 'Cancelar', + delete: 'Excluir', + edit: 'Editar', + add: 'Adicionar', + search: 'Buscar', + loading: 'Carregando...', + error: 'Erro', + success: 'Sucesso', + confirm: 'Confirmar', + back: 'Voltar', + next: 'Proximo', + close: 'Fechar', + yes: 'Sim', + no: 'Nao', + all: 'Todos', + none: 'Nenhum', + required: 'Obrigatorio', + optional: 'Opcional', + }, + + // Navigation + nav: { + dashboard: 'Painel', + products: 'Produtos', + orders: 'Pedidos', + customers: 'Clientes', + fiado: 'Fiado', + inventory: 'Estoque', + referrals: 'Indicacoes', + settings: 'Configuracoes', + logout: 'Sair', + }, + + // Auth + auth: { + login: 'Entrar', + register: 'Cadastrar', + phone: 'Celular', + pin: 'PIN', + enterPhone: 'Digite seu celular', + enterPin: 'Digite seu PIN', + forgotPin: 'Esqueci meu PIN', + noAccount: 'Nao tem conta?', + hasAccount: 'Ja tem conta?', + createAccount: 'Criar conta', + businessName: 'Nome do negocio', + ownerName: 'Seu nome', + businessType: 'Tipo de negocio', + }, + + // Dashboard + dashboard: { + title: 'Painel', + todaySales: 'Vendas de hoje', + weekSales: 'Vendas da semana', + monthSales: 'Vendas do mes', + transactions: 'transacoes', + lowStock: 'Estoque baixo', + pendingOrders: 'Pedidos pendentes', + pendingCredits: 'Fiados pendentes', + topProducts: 'Produtos mais vendidos', + }, + + // Products + products: { + title: 'Produtos', + subtitle: 'Gerencie seu catalogo', + addProduct: 'Adicionar produto', + editProduct: 'Editar produto', + name: 'Nome', + price: 'Preco', + cost: 'Custo', + stock: 'Estoque', + sku: 'SKU', + barcode: 'Codigo de barras', + category: 'Categoria', + description: 'Descricao', + noProducts: 'Nenhum produto', + scanBarcode: 'Escanear codigo', + }, + + // Orders + orders: { + title: 'Pedidos', + subtitle: 'Gerencie os pedidos', + newOrder: 'Novo pedido', + orderNumber: 'Pedido #{{number}}', + status: 'Status', + pending: 'Pendente', + preparing: 'Preparando', + ready: 'Pronto', + delivered: 'Entregue', + cancelled: 'Cancelado', + total: 'Total', + items: 'itens', + noOrders: 'Nenhum pedido', + }, + + // Customers + customers: { + title: 'Clientes', + subtitle: 'Gerencie seus clientes', + addCustomer: 'Adicionar cliente', + name: 'Nome', + phone: 'Celular', + email: 'Email', + creditLimit: 'Limite de credito', + currentBalance: 'Saldo atual', + noCustomers: 'Nenhum cliente', + }, + + // Fiado (Credit) + fiado: { + title: 'Fiado', + subtitle: 'Controle de creditos', + totalOwed: 'Total devido', + overdueAmount: 'Vencido', + customersWithCredit: 'Clientes com fiado', + registerPayment: 'Registrar pagamento', + paymentHistory: 'Historico de pagamentos', + dueDate: 'Data de vencimento', + overdue: 'Vencido', + noCredits: 'Nenhum fiado pendente', + }, + + // Inventory + inventory: { + title: 'Estoque', + subtitle: 'Controle de estoque', + movements: 'Movimentacoes', + addMovement: 'Adicionar movimentacao', + entry: 'Entrada', + exit: 'Saida', + adjustment: 'Ajuste', + lowStockAlerts: 'Alertas de estoque baixo', + reorder: 'Repor', + noMovements: 'Nenhuma movimentacao', + }, + + // Referrals + referrals: { + title: 'Programa de Indicacoes', + subtitle: 'Indique amigos e ganhe', + yourCode: 'Seu codigo de indicacao', + copy: 'Copiar', + share: 'Compartilhar', + shareWhatsApp: 'Compartilhar por WhatsApp', + invited: 'Indicados', + converted: 'Convertidos', + monthsEarned: 'Meses ganhos', + monthsAvailable: 'Disponiveis', + howItWorks: 'Como funciona', + step1: 'Compartilhe seu codigo', + step1Desc: 'Envie seu codigo para amigos por WhatsApp', + step2: 'Seu amigo se cadastra', + step2Desc: 'Ganha 50% de desconto no primeiro mes', + step3: 'Voce ganha 1 mes gratis', + step3Desc: 'Quando seu amigo paga o primeiro mes', + yourReferrals: 'Suas indicacoes', + noReferrals: 'Voce ainda nao tem indicacoes', + }, + + // Settings + settings: { + title: 'Configuracoes', + subtitle: 'Configure sua loja', + businessInfo: 'Informacoes do negocio', + fiadoSettings: 'Configuracao de fiado', + whatsapp: 'WhatsApp Business', + notifications: 'Notificacoes', + subscription: 'Assinatura', + language: 'Idioma', + enableFiado: 'Habilitar fiado', + defaultCreditLimit: 'Limite de credito padrao', + gracePeriod: 'Dias de carencia', + connected: 'Conectado', + autoResponses: 'Respostas automaticas', + lowStockAlerts: 'Alertas de estoque baixo', + overdueAlerts: 'Alertas de fiados vencidos', + newOrderAlerts: 'Alertas de novos pedidos', + currentPlan: 'Plano atual', + upgradePlan: 'Melhorar plano', + }, + + // Payments + payments: { + cash: 'Dinheiro', + card: 'Cartao', + transfer: 'Transferencia', + fiado: 'Fiado', + codi: 'Pix', + spei: 'TED', + change: 'Troco', + total: 'Total', + subtotal: 'Subtotal', + tax: 'Impostos', + discount: 'Desconto', + payNow: 'Pagar agora', + generateQR: 'Gerar QR', + scanQR: 'Escaneie o QR com seu app do banco', + paymentReceived: 'Pagamento recebido', + paymentFailed: 'Pagamento falhou', + }, + + // Errors + errors: { + generic: 'Algo deu errado', + networkError: 'Erro de conexao', + unauthorized: 'Nao autorizado', + notFound: 'Nao encontrado', + validationError: 'Erro de validacao', + serverError: 'Erro do servidor', + }, + + // Business Types + businessTypes: { + tienda: 'Loja', + papeleria: 'Papelaria', + farmacia: 'Farmacia', + ferreteria: 'Ferragem', + carniceria: 'Acougue', + verduleria: 'Hortifruti', + panaderia: 'Padaria', + tortilleria: 'Tapiocaria', + otro: 'Outro', + }, +}; + +export default ptBR; diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/pages/Customers.tsx b/src/pages/Customers.tsx new file mode 100644 index 0000000..ba759aa --- /dev/null +++ b/src/pages/Customers.tsx @@ -0,0 +1,134 @@ +import { useState } from 'react'; +import { Search, Plus, Phone, Mail, MapPin, CreditCard } from 'lucide-react'; + +const mockCustomers = [ + { + id: '1', + name: 'Maria Lopez', + phone: '5551234567', + email: 'maria@email.com', + address: 'Calle 1 #123', + totalPurchases: 45, + totalSpent: 3450.00, + fiadoBalance: 150.00, + fiadoLimit: 500.00, + lastVisit: '2024-01-15', + }, + { + id: '2', + name: 'Juan Perez', + phone: '5559876543', + email: null, + address: null, + totalPurchases: 23, + totalSpent: 1890.00, + fiadoBalance: 0, + fiadoLimit: 300.00, + lastVisit: '2024-01-14', + }, + { + id: '3', + name: 'Ana Garcia', + phone: '5555555555', + email: 'ana@email.com', + address: 'Av. Principal #456', + totalPurchases: 67, + totalSpent: 5670.00, + fiadoBalance: 320.00, + fiadoLimit: 1000.00, + lastVisit: '2024-01-15', + }, +]; + +export function Customers() { + const [search, setSearch] = useState(''); + + const filteredCustomers = mockCustomers.filter((customer) => + customer.name.toLowerCase().includes(search.toLowerCase()) || + customer.phone.includes(search) + ); + + return ( +
+
+
+

Clientes

+

{mockCustomers.length} clientes registrados

+
+ +
+ + {/* Search */} +
+ + setSearch(e.target.value)} + className="input pl-10" + /> +
+ + {/* Customers List */} +
+ {filteredCustomers.map((customer) => ( +
+
+
+

{customer.name}

+
+ + {customer.phone} +
+ {customer.email && ( +
+ + {customer.email} +
+ )} + {customer.address && ( +
+ + {customer.address} +
+ )} +
+
+

Ultima visita

+

{customer.lastVisit}

+
+
+ +
+
+

Compras

+

{customer.totalPurchases}

+
+
+

Total gastado

+

${customer.totalSpent.toFixed(0)}

+
+
+

Fiado

+
+ 0 ? 'text-orange-500' : 'text-green-500' + }`} /> +

0 ? 'text-orange-600' : 'text-green-600' + }`}> + ${customer.fiadoBalance.toFixed(0)} +

+
+
+
+
+ ))} +
+
+ ); +} diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..477d4e6 --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -0,0 +1,131 @@ +import { + TrendingUp, + ShoppingCart, + Users, + CreditCard, + Package, + AlertCircle, +} from 'lucide-react'; + +const stats = [ + { name: 'Ventas Hoy', value: '$1,240', change: '+12%', icon: TrendingUp, color: 'green' }, + { name: 'Pedidos', value: '23', change: '+5', icon: ShoppingCart, color: 'blue' }, + { name: 'Clientes', value: '156', change: '+3', icon: Users, color: 'purple' }, + { name: 'Fiados Pendientes', value: '$2,100', change: '-$450', icon: CreditCard, color: 'orange' }, +]; + +const recentOrders = [ + { id: 'MCH-001', customer: 'Maria Lopez', total: 156.00, status: 'ready', time: '10:30 AM' }, + { id: 'MCH-002', customer: 'Juan Perez', total: 89.50, status: 'preparing', time: '10:45 AM' }, + { id: 'MCH-003', customer: 'Ana Garcia', total: 234.00, status: 'pending', time: '11:00 AM' }, +]; + +const lowStockProducts = [ + { name: 'Coca-Cola 600ml', stock: 5, minStock: 10 }, + { name: 'Pan Bimbo', stock: 2, minStock: 5 }, + { name: 'Leche Lala 1L', stock: 3, minStock: 8 }, +]; + +const statusColors: Record = { + pending: 'bg-yellow-100 text-yellow-800', + preparing: 'bg-blue-100 text-blue-800', + ready: 'bg-green-100 text-green-800', + completed: 'bg-gray-100 text-gray-800', +}; + +export function Dashboard() { + return ( +
+
+

Dashboard

+

Bienvenido a MiChangarrito

+
+ + {/* Stats Grid */} +
+ {stats.map((stat) => ( +
+
+
+

{stat.name}

+

{stat.value}

+

+ {stat.change} +

+
+
+ +
+
+
+ ))} +
+ +
+ {/* Recent Orders */} +
+
+

+ + Pedidos Recientes +

+ + Ver todos + +
+
+ {recentOrders.map((order) => ( +
+
+

{order.id}

+

{order.customer}

+
+
+

${order.total.toFixed(2)}

+ + {order.status} + +
+
+ ))} +
+
+ + {/* Low Stock Alert */} +
+
+

+ + Stock Bajo +

+ + Ver inventario + +
+
+ {lowStockProducts.map((product) => ( +
+
+ +

{product.name}

+
+
+

{product.stock} unidades

+

Min: {product.minStock}

+
+
+ ))} +
+
+
+
+ ); +} diff --git a/src/pages/Fiado.tsx b/src/pages/Fiado.tsx new file mode 100644 index 0000000..19a75c7 --- /dev/null +++ b/src/pages/Fiado.tsx @@ -0,0 +1,180 @@ +import { useState } from 'react'; +import { CreditCard, AlertTriangle, CheckCircle, Clock, Plus } from 'lucide-react'; +import clsx from 'clsx'; + +const mockFiados = [ + { + id: '1', + customer: 'Maria Lopez', + phone: '5551234567', + amount: 150.00, + description: 'Compra del 15/01', + status: 'pending', + dueDate: '2024-01-30', + createdAt: '2024-01-15', + }, + { + id: '2', + customer: 'Ana Garcia', + phone: '5555555555', + amount: 320.00, + description: 'Productos varios', + status: 'overdue', + dueDate: '2024-01-10', + createdAt: '2024-01-01', + }, + { + id: '3', + customer: 'Pedro Martinez', + phone: '5553334444', + amount: 89.50, + description: 'Bebidas y botanas', + status: 'pending', + dueDate: '2024-02-01', + createdAt: '2024-01-14', + }, +]; + +const recentPayments = [ + { customer: 'Juan Perez', amount: 200.00, date: '2024-01-15' }, + { customer: 'Laura Sanchez', amount: 150.00, date: '2024-01-14' }, + { customer: 'Carlos Ruiz', amount: 75.00, date: '2024-01-13' }, +]; + +export function Fiado() { + const [filter, setFilter] = useState<'all' | 'pending' | 'overdue'>('all'); + + const totalPending = mockFiados.reduce((sum, f) => sum + f.amount, 0); + const overdueCount = mockFiados.filter(f => f.status === 'overdue').length; + + const filteredFiados = filter === 'all' + ? mockFiados + : mockFiados.filter(f => f.status === filter); + + return ( +
+
+
+

Fiado

+

Gestiona las cuentas de credito de tus clientes

+
+ +
+ + {/* Summary Cards */} +
+
+
+ +
+

Total Pendiente

+

${totalPending.toFixed(2)}

+
+
+
+
+
+ +
+

Vencidos

+

{overdueCount} cuentas

+
+
+
+
+
+ +
+

Cobrado este mes

+

$425.00

+
+
+
+
+ +
+ {/* Fiados List */} +
+
+ {(['all', 'pending', 'overdue'] as const).map((status) => ( + + ))} +
+ + {filteredFiados.map((fiado) => ( +
+
+
+

{fiado.customer}

+

{fiado.phone}

+

{fiado.description}

+
+
+

+ ${fiado.amount.toFixed(2)} +

+
+ {fiado.status === 'overdue' ? ( + + ) : ( + + )} + {fiado.status === 'overdue' ? 'Vencido' : 'Pendiente'} +
+
+
+
+
+ Creado: {fiado.createdAt} + | + Vence: {fiado.dueDate} +
+
+ + +
+
+
+ ))} +
+ + {/* Recent Payments */} +
+

Pagos Recientes

+
+ {recentPayments.map((payment, i) => ( +
+
+

{payment.customer}

+

{payment.date}

+
+

+${payment.amount.toFixed(2)}

+
+ ))} +
+
+
+
+ ); +} diff --git a/src/pages/Inventory.tsx b/src/pages/Inventory.tsx new file mode 100644 index 0000000..49bb256 --- /dev/null +++ b/src/pages/Inventory.tsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import { Package, TrendingUp, TrendingDown, AlertTriangle, Plus, Minus } from 'lucide-react'; +import clsx from 'clsx'; + +const mockInventory = [ + { id: '1', name: 'Coca-Cola 600ml', category: 'bebidas', stock: 24, minStock: 10, maxStock: 50, cost: 12.00, price: 18.00 }, + { id: '2', name: 'Sabritas Original', category: 'botanas', stock: 12, minStock: 5, maxStock: 30, cost: 10.00, price: 15.00 }, + { id: '3', name: 'Leche Lala 1L', category: 'lacteos', stock: 3, minStock: 8, maxStock: 20, cost: 22.00, price: 28.00 }, + { id: '4', name: 'Pan Bimbo Grande', category: 'panaderia', stock: 2, minStock: 5, maxStock: 15, cost: 35.00, price: 45.00 }, + { id: '5', name: 'Fabuloso 1L', category: 'limpieza', stock: 6, minStock: 5, maxStock: 20, cost: 25.00, price: 32.00 }, +]; + +const recentMovements = [ + { product: 'Coca-Cola 600ml', type: 'sale', quantity: -2, date: '10:30 AM' }, + { product: 'Sabritas', type: 'sale', quantity: -1, date: '10:15 AM' }, + { product: 'Leche Lala 1L', type: 'purchase', quantity: +12, date: '09:00 AM' }, + { product: 'Pan Bimbo', type: 'sale', quantity: -3, date: '08:45 AM' }, +]; + +export function Inventory() { + const [showLowStock, setShowLowStock] = useState(false); + + const lowStockItems = mockInventory.filter(item => item.stock <= item.minStock); + const totalValue = mockInventory.reduce((sum, item) => sum + (item.stock * item.cost), 0); + const totalItems = mockInventory.reduce((sum, item) => sum + item.stock, 0); + + const displayItems = showLowStock + ? lowStockItems + : mockInventory; + + return ( +
+
+
+

Inventario

+

Control de existencias

+
+
+ + +
+
+ + {/* Summary */} +
+
+
+ +
+

Total Unidades

+

{totalItems}

+
+
+
+
+
+ +
+

Valor del Inventario

+

${totalValue.toFixed(2)}

+
+
+
+
setShowLowStock(!showLowStock)} + > +
+ +
+

Stock Bajo

+

{lowStockItems.length} productos

+
+
+
+
+ +
+ {/* Inventory Table */} +
+
+ + + + + + + + + + + {displayItems.map((item) => { + const isLow = item.stock <= item.minStock; + const percentage = (item.stock / item.maxStock) * 100; + + return ( + + + + + + + ); + })} + +
ProductoStockMin/MaxValor
+

{item.name}

+

{item.category}

+
+
+ + {item.stock} + +
+
+
+
+
+ {item.minStock} / {item.maxStock} + + ${(item.stock * item.cost).toFixed(2)} +
+
+
+ + {/* Recent Movements */} +
+

Movimientos Recientes

+
+ {recentMovements.map((mov, i) => ( +
+
+ {mov.type === 'sale' ? ( + + ) : ( + + )} +
+

{mov.product}

+

{mov.date}

+
+
+ 0 ? 'text-green-600' : 'text-red-600' + )}> + {mov.quantity > 0 ? '+' : ''}{mov.quantity} + +
+ ))} +
+
+
+
+ ); +} diff --git a/src/pages/Invoices.tsx b/src/pages/Invoices.tsx new file mode 100644 index 0000000..a30e90b --- /dev/null +++ b/src/pages/Invoices.tsx @@ -0,0 +1,668 @@ +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + FileText, + Send, + XCircle, + CheckCircle, + Clock, + Download, + Search, + Plus, + Settings, + AlertCircle, +} from 'lucide-react'; +import clsx from 'clsx'; +import { invoicesApi } from '../lib/api'; + +const statusConfig = { + draft: { label: 'Borrador', color: 'gray', icon: Clock }, + pending: { label: 'Pendiente', color: 'yellow', icon: Clock }, + stamped: { label: 'Timbrada', color: 'green', icon: CheckCircle }, + sent: { label: 'Enviada', color: 'blue', icon: Send }, + cancelled: { label: 'Cancelada', color: 'red', icon: XCircle }, +}; + +export function Invoices() { + const [filter, setFilter] = useState('all'); + const [showConfig, setShowConfig] = useState(false); + const [showNewInvoice, setShowNewInvoice] = useState(false); + const queryClient = useQueryClient(); + + const { data: invoices = [], isLoading } = useQuery({ + queryKey: ['invoices', filter], + queryFn: async () => { + const params = filter !== 'all' ? { status: filter } : {}; + const response = await invoicesApi.getAll(params); + return response.data; + }, + }); + + const { data: summary } = useQuery({ + queryKey: ['invoices-summary'], + queryFn: async () => { + const response = await invoicesApi.getSummary(); + return response.data; + }, + }); + + const { data: taxConfig } = useQuery({ + queryKey: ['tax-config'], + queryFn: async () => { + const response = await invoicesApi.getTaxConfig(); + return response.data; + }, + }); + + const stampMutation = useMutation({ + mutationFn: (id: string) => invoicesApi.stamp(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['invoices'] }); + }, + }); + + const sendMutation = useMutation({ + mutationFn: (id: string) => invoicesApi.send(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['invoices'] }); + }, + }); + + const hasActiveConfig = taxConfig?.status === 'active'; + + return ( +
+
+
+

Facturacion

+

Emite facturas electronicas (CFDI)

+
+
+ + +
+
+ + {/* Alert if no config */} + {!hasActiveConfig && ( +
+ +
+

Configuracion fiscal requerida

+

+ Para emitir facturas, primero configura tus datos fiscales y certificados. +

+
+
+ )} + + {/* Summary Cards */} +
+
+

Facturas del mes

+

{summary?.total_invoices || 0}

+
+
+

Monto facturado

+

+ ${(summary?.total_amount || 0).toLocaleString('es-MX', { minimumFractionDigits: 2 })} +

+
+
+

Canceladas

+

{summary?.total_cancelled || 0}

+
+
+

RFC Emisor

+

{taxConfig?.rfc || 'No configurado'}

+
+
+ + {/* Status Filter */} +
+ + {Object.entries(statusConfig).map(([status, config]) => ( + + ))} +
+ + {/* Invoices List */} + {isLoading ? ( +
+
+

Cargando facturas...

+
+ ) : invoices.length === 0 ? ( +
+ +

No hay facturas

+

Las facturas emitidas apareceran aqui

+
+ ) : ( +
+ {invoices.map((invoice: any) => { + const status = statusConfig[invoice.status as keyof typeof statusConfig] || statusConfig.draft; + const StatusIcon = status.icon; + + return ( +
+
+
+
+ +
+
+
+

+ {invoice.serie}-{invoice.folio} +

+ + {status.label} + +
+

{invoice.receptorNombre}

+

{invoice.receptorRfc}

+ {invoice.uuid && ( +

+ UUID: {invoice.uuid} +

+ )} +
+
+ +
+
+

+ ${Number(invoice.total).toLocaleString('es-MX', { minimumFractionDigits: 2 })} +

+

+ {new Date(invoice.createdAt).toLocaleDateString('es-MX')} +

+
+ +
+ {invoice.status === 'draft' && ( + + )} + {invoice.status === 'stamped' && ( + + )} + {(invoice.status === 'stamped' || invoice.status === 'sent') && ( + + )} +
+
+
+
+ ); + })} +
+ )} + + {/* Tax Config Modal */} + {showConfig && ( + setShowConfig(false)} + onSave={() => { + queryClient.invalidateQueries({ queryKey: ['tax-config'] }); + setShowConfig(false); + }} + /> + )} + + {/* New Invoice Modal */} + {showNewInvoice && ( + setShowNewInvoice(false)} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['invoices'] }); + setShowNewInvoice(false); + }} + /> + )} +
+ ); +} + +function TaxConfigModal({ + config, + onClose, + onSave, +}: { + config: any; + onClose: () => void; + onSave: () => void; +}) { + const [formData, setFormData] = useState({ + rfc: config?.rfc || '', + razonSocial: config?.razonSocial || '', + regimenFiscal: config?.regimenFiscal || '601', + codigoPostal: config?.codigoPostal || '', + serie: config?.serie || 'A', + pacProvider: config?.pacProvider || 'facturapi', + pacSandbox: config?.pacSandbox ?? true, + }); + + const mutation = useMutation({ + mutationFn: (data: any) => invoicesApi.saveTaxConfig(data), + onSuccess: onSave, + }); + + return ( +
+
+
+

Configuracion Fiscal

+
+ +
{ + e.preventDefault(); + mutation.mutate(formData); + }} + className="p-6 space-y-4" + > +
+ + setFormData({ ...formData, rfc: e.target.value.toUpperCase() })} + className="input" + maxLength={13} + required + /> +
+ +
+ + setFormData({ ...formData, razonSocial: e.target.value })} + className="input" + required + /> +
+ +
+
+ + +
+ +
+ + setFormData({ ...formData, codigoPostal: e.target.value })} + className="input" + maxLength={5} + required + /> +
+
+ +
+
+ + setFormData({ ...formData, serie: e.target.value.toUpperCase() })} + className="input" + maxLength={10} + /> +
+ +
+ + +
+
+ +
+ setFormData({ ...formData, pacSandbox: e.target.checked })} + className="rounded border-gray-300" + /> + +
+ +
+ + +
+
+
+
+ ); +} + +function NewInvoiceModal({ + onClose, + onSuccess, +}: { + onClose: () => void; + onSuccess: () => void; +}) { + const [formData, setFormData] = useState({ + receptorRfc: '', + receptorNombre: '', + receptorRegimenFiscal: '601', + receptorCodigoPostal: '', + receptorUsoCfdi: 'G03', + receptorEmail: '', + formaPago: '01', + metodoPago: 'PUE', + items: [{ descripcion: '', cantidad: 1, valorUnitario: 0, claveProdServ: '01010101', claveUnidad: 'H87' }], + }); + + const mutation = useMutation({ + mutationFn: (data: any) => invoicesApi.create(data), + onSuccess, + }); + + const addItem = () => { + setFormData({ + ...formData, + items: [ + ...formData.items, + { descripcion: '', cantidad: 1, valorUnitario: 0, claveProdServ: '01010101', claveUnidad: 'H87' }, + ], + }); + }; + + const updateItem = (index: number, field: string, value: any) => { + const newItems = [...formData.items]; + newItems[index] = { ...newItems[index], [field]: value }; + setFormData({ ...formData, items: newItems }); + }; + + const removeItem = (index: number) => { + if (formData.items.length > 1) { + setFormData({ + ...formData, + items: formData.items.filter((_, i) => i !== index), + }); + } + }; + + const total = formData.items.reduce( + (sum, item) => sum + item.cantidad * item.valorUnitario * 1.16, + 0 + ); + + return ( +
+
+
+

Nueva Factura

+
+ +
{ + e.preventDefault(); + mutation.mutate(formData); + }} + className="p-6 space-y-6" + > + {/* Receptor */} +
+

Datos del Receptor

+
+
+ + setFormData({ ...formData, receptorRfc: e.target.value.toUpperCase() })} + className="input" + maxLength={13} + required + /> +
+
+ + setFormData({ ...formData, receptorNombre: e.target.value })} + className="input" + required + /> +
+
+ + setFormData({ ...formData, receptorCodigoPostal: e.target.value })} + className="input" + maxLength={5} + required + /> +
+
+ + +
+
+ + setFormData({ ...formData, receptorEmail: e.target.value })} + className="input" + placeholder="cliente@email.com" + /> +
+
+
+ + {/* Pago */} +
+

Metodo de Pago

+
+
+ + +
+
+ + +
+
+
+ + {/* Items */} +
+
+

Conceptos

+ +
+ +
+ {formData.items.map((item, index) => ( +
+
+
+ updateItem(index, 'descripcion', e.target.value)} + className="input" + placeholder="Descripcion" + required + /> +
+
+ updateItem(index, 'cantidad', Number(e.target.value))} + className="input" + placeholder="Cant" + min={1} + required + /> +
+
+ updateItem(index, 'valorUnitario', Number(e.target.value))} + className="input" + placeholder="Precio" + min={0} + step={0.01} + required + /> +
+
+ {formData.items.length > 1 && ( + + )} +
+
+
+ ))} +
+
+ + {/* Total */} +
+

Total (IVA incluido)

+

+ ${total.toLocaleString('es-MX', { minimumFractionDigits: 2 })} +

+
+ +
+ + +
+
+
+
+ ); +} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..cfe8dee --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,103 @@ +import { useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import { useAuth } from '../contexts/AuthContext'; + +export default function Login() { + const [phone, setPhone] = useState(''); + const [pin, setPin] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const { login } = useAuth(); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + await login(phone, pin); + navigate('/'); + } catch (err: any) { + setError(err.response?.data?.message || 'Error al iniciar sesion'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+

+ MiChangarrito +

+

+ Iniciar sesion +

+

+ O{' '} + + registra tu negocio + +

+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+
+ + setPhone(e.target.value.replace(/\D/g, ''))} + /> +
+ +
+ + setPin(e.target.value.replace(/\D/g, ''))} + /> +
+
+ + +
+
+
+ ); +} diff --git a/src/pages/Marketplace.tsx b/src/pages/Marketplace.tsx new file mode 100644 index 0000000..84d6c61 --- /dev/null +++ b/src/pages/Marketplace.tsx @@ -0,0 +1,731 @@ +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { + Store, + Search, + Star, + Heart, + ShoppingCart, + Package, + Truck, + Clock, + CheckCircle, + XCircle, + MapPin, + Phone, + ChevronRight, + Filter, +} from 'lucide-react'; +import clsx from 'clsx'; +import { marketplaceApi } from '../lib/api'; + +const categories = [ + { id: 'bebidas', name: 'Bebidas', icon: '🥤' }, + { id: 'botanas', name: 'Botanas', icon: '🍿' }, + { id: 'lacteos', name: 'Lacteos', icon: '🥛' }, + { id: 'pan', name: 'Pan', icon: '🍞' }, + { id: 'abarrotes', name: 'Abarrotes', icon: '🛒' }, + { id: 'limpieza', name: 'Limpieza', icon: '🧹' }, +]; + +const orderStatusConfig = { + pending: { label: 'Pendiente', color: 'yellow', icon: Clock }, + confirmed: { label: 'Confirmado', color: 'blue', icon: CheckCircle }, + preparing: { label: 'Preparando', color: 'indigo', icon: Package }, + shipped: { label: 'En camino', color: 'purple', icon: Truck }, + delivered: { label: 'Entregado', color: 'green', icon: CheckCircle }, + cancelled: { label: 'Cancelado', color: 'red', icon: XCircle }, + rejected: { label: 'Rechazado', color: 'red', icon: XCircle }, +}; + +export function Marketplace() { + const [view, setView] = useState<'suppliers' | 'orders' | 'favorites'>('suppliers'); + const [selectedCategory, setSelectedCategory] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedSupplier, setSelectedSupplier] = useState(null); + const [showCart, setShowCart] = useState(false); + const [cart, setCart] = useState<{ product: any; quantity: number }[]>([]); + const queryClient = useQueryClient(); + + const { data: suppliers = [], isLoading: loadingSuppliers } = useQuery({ + queryKey: ['suppliers', selectedCategory, searchQuery], + queryFn: async () => { + const response = await marketplaceApi.getSuppliers({ + category: selectedCategory || undefined, + search: searchQuery || undefined, + }); + return response.data; + }, + }); + + const { data: orders = [], isLoading: loadingOrders } = useQuery({ + queryKey: ['marketplace-orders'], + queryFn: async () => { + const response = await marketplaceApi.getOrders(); + return response.data; + }, + enabled: view === 'orders', + }); + + const { data: favorites = [] } = useQuery({ + queryKey: ['supplier-favorites'], + queryFn: async () => { + const response = await marketplaceApi.getFavorites(); + return response.data; + }, + enabled: view === 'favorites', + }); + + const addToCart = (product: any) => { + const existing = cart.find((item) => item.product.id === product.id); + if (existing) { + setCart( + cart.map((item) => + item.product.id === product.id + ? { ...item, quantity: item.quantity + 1 } + : item + ) + ); + } else { + setCart([...cart, { product, quantity: 1 }]); + } + }; + + const removeFromCart = (productId: string) => { + setCart(cart.filter((item) => item.product.id !== productId)); + }; + + const updateCartQuantity = (productId: string, quantity: number) => { + if (quantity <= 0) { + removeFromCart(productId); + } else { + setCart( + cart.map((item) => + item.product.id === productId ? { ...item, quantity } : item + ) + ); + } + }; + + const cartTotal = cart.reduce( + (sum, item) => sum + Number(item.product.unitPrice) * item.quantity, + 0 + ); + + return ( +
+
+
+

Marketplace

+

Encuentra proveedores para tu negocio

+
+ + {cart.length > 0 && ( + + )} +
+ + {/* Tabs */} +
+ + + +
+ + {view === 'suppliers' && ( + <> + {/* Search & Filter */} +
+
+ + setSearchQuery(e.target.value)} + placeholder="Buscar proveedores..." + className="input pl-10" + /> +
+
+ + {/* Categories */} +
+ + {categories.map((category) => ( + + ))} +
+ + {/* Suppliers Grid */} + {loadingSuppliers ? ( +
+
+

Buscando proveedores...

+
+ ) : suppliers.length === 0 ? ( +
+ +

No hay proveedores

+

Pronto habra mas proveedores disponibles

+
+ ) : ( +
+ {suppliers.map((supplier: any) => ( + setSelectedSupplier(supplier)} + /> + ))} +
+ )} + + )} + + {view === 'orders' && ( +
+ {loadingOrders ? ( +
+
+
+ ) : orders.length === 0 ? ( +
+ +

No hay pedidos

+

Tus pedidos a proveedores apareceran aqui

+
+ ) : ( + orders.map((order: any) => ( + + )) + )} +
+ )} + + {view === 'favorites' && ( +
+ {favorites.length === 0 ? ( +
+ +

Sin favoritos

+

Agrega proveedores a tus favoritos

+
+ ) : ( + favorites.map((supplier: any) => ( + setSelectedSupplier(supplier)} + /> + )) + )} +
+ )} + + {/* Supplier Detail Modal */} + {selectedSupplier && ( + setSelectedSupplier(null)} + onAddToCart={addToCart} + cart={cart} + /> + )} + + {/* Cart Modal */} + {showCart && ( + setShowCart(false)} + onUpdateQuantity={updateCartQuantity} + onRemove={removeFromCart} + onOrderSuccess={() => { + setCart([]); + setShowCart(false); + queryClient.invalidateQueries({ queryKey: ['marketplace-orders'] }); + }} + /> + )} +
+ ); +} + +function SupplierCard({ + supplier, + onClick, +}: { + supplier: any; + onClick: () => void; +}) { + return ( +
+
+
+ {supplier.logoUrl ? ( + {supplier.name} + ) : ( + + )} +
+
+
+

{supplier.name}

+ {supplier.verified && ( + + )} +
+
+
+ + {Number(supplier.rating).toFixed(1)} +
+ · + {supplier.totalReviews} resenas +
+
+ {supplier.categories?.slice(0, 3).map((cat: string) => ( + + {cat} + + ))} +
+
+ Min: ${Number(supplier.minOrderAmount).toFixed(0)} + {Number(supplier.deliveryFee) > 0 ? ( + Envio: ${Number(supplier.deliveryFee).toFixed(0)} + ) : ( + Envio gratis + )} +
+
+ +
+
+ ); +} + +function OrderCard({ order }: { order: any }) { + const status = orderStatusConfig[order.status as keyof typeof orderStatusConfig] || orderStatusConfig.pending; + const StatusIcon = status.icon; + + return ( +
+
+
+
+ +
+
+
+

Pedido #{order.orderNumber}

+ + {status.label} + +
+

{order.supplier?.name}

+

+ {order.items?.length} productos +

+
+
+ +
+

+ ${Number(order.total).toLocaleString('es-MX', { minimumFractionDigits: 2 })} +

+

+ {new Date(order.createdAt).toLocaleDateString('es-MX')} +

+
+
+
+ ); +} + +function SupplierDetailModal({ + supplier, + onClose, + onAddToCart, + cart, +}: { + supplier: any; + onClose: () => void; + onAddToCart: (product: any) => void; + cart: { product: any; quantity: number }[]; +}) { + const [search, setSearch] = useState(''); + const queryClient = useQueryClient(); + + const { data: products = [], isLoading } = useQuery({ + queryKey: ['supplier-products', supplier.id, search], + queryFn: async () => { + const response = await marketplaceApi.getSupplierProducts(supplier.id, { + search: search || undefined, + }); + return response.data; + }, + }); + + const favoriteMutation = useMutation({ + mutationFn: () => marketplaceApi.addFavorite(supplier.id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['supplier-favorites'] }); + }, + }); + + const getCartQuantity = (productId: string) => { + const item = cart.find((i) => i.product.id === productId); + return item?.quantity || 0; + }; + + return ( +
+
+ {/* Header */} +
+
+
+ {supplier.logoUrl ? ( + {supplier.name} + ) : ( + + )} +
+
+
+

{supplier.name}

+ +
+
+ + {Number(supplier.rating).toFixed(1)} + · + {supplier.totalReviews} resenas +
+ {supplier.address && ( +

+ + {supplier.city}, {supplier.state} +

+ )} +
+
+ +
+ + {supplier.contactPhone && ( + + + Llamar + + )} +
+
+ + {/* Search */} +
+
+ + setSearch(e.target.value)} + placeholder="Buscar productos..." + className="input pl-10" + /> +
+
+ + {/* Products */} +
+ {isLoading ? ( +
+
+
+ ) : products.length === 0 ? ( +
+ No hay productos disponibles +
+ ) : ( +
+ {products.map((product: any) => { + const inCart = getCartQuantity(product.id); + return ( +
+
+ {product.imageUrl ? ( + {product.name} + ) : ( + + )} +
+
+

{product.name}

+

+ Min: {product.minQuantity} {product.unitType} +

+
+ + ${Number(product.unitPrice).toFixed(2)} + + {inCart > 0 ? ( + + {inCart} en carrito + + ) : ( + + )} +
+
+
+ ); + })} +
+ )} +
+
+
+ ); +} + +function CartModal({ + cart, + supplier, + onClose, + onUpdateQuantity, + onRemove, + onOrderSuccess, +}: { + cart: { product: any; quantity: number }[]; + supplier: any; + onClose: () => void; + onUpdateQuantity: (productId: string, quantity: number) => void; + onRemove: (productId: string) => void; + onOrderSuccess: () => void; +}) { + const [deliveryAddress, setDeliveryAddress] = useState(''); + const [deliveryPhone, setDeliveryPhone] = useState(''); + const [notes, setNotes] = useState(''); + + const subtotal = cart.reduce( + (sum, item) => sum + Number(item.product.unitPrice) * item.quantity, + 0 + ); + + const orderMutation = useMutation({ + mutationFn: (data: any) => marketplaceApi.createOrder(data), + onSuccess: onOrderSuccess, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const supplierId = cart[0]?.product?.supplierId; + if (!supplierId) return; + + orderMutation.mutate({ + supplierId, + items: cart.map((item) => ({ + productId: item.product.id, + quantity: item.quantity, + })), + deliveryAddress, + deliveryPhone, + notes, + }); + }; + + return ( +
+
+
+

Tu Carrito

+ +
+ +
+ {/* Items */} +
+ {cart.map((item) => ( +
+
+

{item.product.name}

+

+ ${Number(item.product.unitPrice).toFixed(2)} x {item.quantity} +

+
+
+ + {item.quantity} + +
+

+ ${(Number(item.product.unitPrice) * item.quantity).toFixed(2)} +

+
+ ))} +
+ + {/* Delivery Info */} +
+

Datos de entrega

+
+ +