diff --git a/.env b/.env
new file mode 100644
index 0000000..013a7ad
--- /dev/null
+++ b/.env
@@ -0,0 +1,9 @@
+SMTP_HOST=smtp.gmail.com
+SMTP_PORT=587
+SMTP_SECURE=false
+RECIPIENT_EMAIL=chris@sigd.net
+GOOGLE_EMAIL=system@deafgain.org
+GOOGLE_APP_PASSWORD=ojvlysraxwjriwzy
+UPSTASH_REDIS_REST_URL=https://gusc1-resolved-jaybird-30779.upstash.io
+UPSTASH_REDIS_REST_TOKEN=AXg7ASQgNDMzMDE0MTYtNWE0Ni00OTc3LThhYjktM2IxMzMxNGMyMzMxNWRiNGMyOGU2MjE0NGUyNTkwMTQyNmUxZTU2NzE4NDI=
+
diff --git a/.gitignore b/.gitignore
index 37d3e57..cc8ac4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,12 +10,6 @@ coverage
dist
build
-# Environment files
-.env
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
# Logs
npm-debug.log*
diff --git a/Dockerfile.api b/Dockerfile.api
new file mode 100644
index 0000000..e128089
--- /dev/null
+++ b/Dockerfile.api
@@ -0,0 +1,19 @@
+FROM node:20-alpine
+
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+WORKDIR /app
+
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install
+
+COPY server.ts ./
+COPY src/api ./src/api
+COPY src/lib ./src/lib
+COPY tsconfig.json tsconfig.server.json ./
+
+RUN pnpm tsc -p tsconfig.server.json
+
+EXPOSE 3000
+
+CMD ["node", "dist/server.js"]
diff --git a/docker-compose.yml b/docker-compose.yml
index 4fcf84c..ffc5129 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,8 +6,8 @@ services:
pull_policy: build
ports:
- "804:804"
- networks:
- - caddy_network
+ depends_on:
+ - api
volumes:
- type: bind
source: /docker/websites/deafgain/video_content
@@ -16,7 +16,31 @@ services:
- NODE_ENV=production
- PORT=804
restart: unless-stopped
+ networks:
+ - caddy_network
+ - default
+
+ api:
+ build:
+ context: .
+ dockerfile: Dockerfile.api
+ environment:
+ - NODE_ENV=production
+ - PORT=3000
+ - SMTP_HOST=${SMTP_HOST}
+ - SMTP_PORT=${SMTP_PORT}
+ - SMTP_SECURE=${SMTP_SECURE}
+ - RECIPIENT_EMAIL=${RECIPIENT_EMAIL}
+ - GOOGLE_EMAIL=${GOOGLE_EMAIL}
+ - GOOGLE_APP_PASSWORD=${GOOGLE_APP_PASSWORD}
+ - UPSTASH_REDIS_REST_URL=${UPSTASH_REDIS_REST_URL}
+ - UPSTASH_REDIS_REST_TOKEN=${UPSTASH_REDIS_REST_TOKEN}
+ restart: unless-stopped
+ networks:
+ - default
networks:
+ default:
+ driver: bridge
caddy_network:
external: true
\ No newline at end of file
diff --git a/docker-compose.yml-backup b/docker-compose.yml-backup
new file mode 100644
index 0000000..01e347d
--- /dev/null
+++ b/docker-compose.yml-backup
@@ -0,0 +1,46 @@
+services:
+ web:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ pull_policy: build
+ ports:
+ - "804:804"
+ depends_on:
+ - api
+ volumes:
+ - ./logs:/var/log/nginx
+ - type: bind
+ source: /docker/websites/deafgain/video_content
+ target: /usr/share/nginx/html/videos
+ environment:
+ - NODE_ENV=production
+ - PORT=804
+ restart: unless-stopped
+ networks:
+ - caddy_network
+
+ api:
+ build:
+ context: .
+ dockerfile: Dockerfile.api
+ volumes:
+ - ./logs:/app/logs
+ environment:
+ - NODE_ENV=production
+ - PORT=3000
+ - SMTP_HOST=${SMTP_HOST}
+ - SMTP_PORT=${SMTP_PORT}
+ - SMTP_SECURE=${SMTP_SECURE}
+ - RECIPIENT_EMAIL=${RECIPIENT_EMAIL}
+ - GOOGLE_EMAIL=${GOOGLE_EMAIL}
+ - GOOGLE_APP_PASSWORD=${GOOGLE_APP_PASSWORD}
+ - UPSTASH_REDIS_REST_URL=${UPSTASH_REDIS_REST_URL}
+ - UPSTASH_REDIS_REST_TOKEN=${UPSTASH_REDIS_REST_TOKEN}
+ restart: unless-stopped
+ networks:
+ - caddy_network
+
+networks:
+ caddy_network:
+ external: true
diff --git a/index.html b/index.html
index 2b639fc..496a998 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,6 @@
-
DeafGain LLC
diff --git a/nginx.conf b/nginx.conf
index ee785af..23a8370 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -4,6 +4,16 @@ server {
root /usr/share/nginx/html;
index index.html;
+ # Production logging configuration
+ access_log /var/log/nginx/api_access.log combined buffer=512k flush=1m;
+ error_log /var/log/nginx/api_error.log error;
+
+ # Increased buffer sizes
+ large_client_header_buffers 8 32k;
+ client_header_buffer_size 32k;
+ client_max_body_size 10M;
+ client_body_buffer_size 128k;
+
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
@@ -15,6 +25,35 @@ server {
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+ # API endpoint
+ location /api/contact {
+ proxy_pass http://api:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ access_log /var/log/nginx/api_access.log combined buffer=512k flush=1m;
+ error_log /var/log/nginx/api_error.log error;
+
+ # CORS headers
+ add_header 'Access-Control-Allow-Origin' '*';
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+ add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+
+ # Handle OPTIONS method
+ if ($request_method = 'OPTIONS') {
+ add_header 'Access-Control-Allow-Origin' '*';
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+ add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+ add_header 'Access-Control-Max-Age' 1728000;
+ add_header 'Content-Type' 'text/plain; charset=utf-8';
+ add_header 'Content-Length' 0;
+ return 204;
+ }
+ }
+
location / {
try_files $uri $uri/ /index.html;
expires 1h;
diff --git a/package.json b/package.json
index cb11afd..df4f88f 100644
--- a/package.json
+++ b/package.json
@@ -4,17 +4,23 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite",
+ "dev": "concurrently \"vite\" \"tsx watch server.ts\"",
"build": "ROLLUP_SKIP_NODE_RESOLVE=true tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
+ "@types/express": "^5.0.0",
"@types/react-router-dom": "^5.3.3",
"@types/react-simple-maps": "^3.0.6",
+ "@upstash/redis": "^1.28.4",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
"framer-motion": "^11.13.5",
+ "next": "^14.1.0",
+ "nodemailer": "^6.9.9",
"postcss": "^8.4.49",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -28,12 +34,16 @@
"devDependencies": {
"@eslint/js": "^9.15.0",
"@types/node": "^22.10.1",
+ "@types/nodemailer": "^6.4.14",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
+ "concurrently": "^8.2.2",
"eslint": "^9.15.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.13.0",
+ "ts-node": "^10.9.2",
+ "tsx": "^4.19.2",
"typescript": "~5.7.2",
"typescript-eslint": "^8.18.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c4818db..1e49778 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,21 +8,39 @@ importers:
.:
dependencies:
+ '@types/express':
+ specifier: ^5.0.0
+ version: 5.0.0
'@types/react-router-dom':
specifier: ^5.3.3
version: 5.3.3
'@types/react-simple-maps':
specifier: ^3.0.6
version: 3.0.6
+ '@upstash/redis':
+ specifier: ^1.28.4
+ version: 1.34.3
'@vitejs/plugin-react':
specifier: ^4.3.4
- version: 4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))
+ version: 4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(tsx@4.19.2)(yaml@2.6.1))
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.49)
+ dotenv:
+ specifier: ^16.4.7
+ version: 16.4.7
+ express:
+ specifier: ^4.21.2
+ version: 4.21.2
framer-motion:
specifier: ^11.13.5
version: 11.13.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ next:
+ specifier: ^14.1.0
+ version: 14.2.20(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ nodemailer:
+ specifier: ^6.9.9
+ version: 6.9.16
postcss:
specifier: ^8.4.49
version: 8.4.49
@@ -46,10 +64,10 @@ importers:
version: 4.0.0-beta.6(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
tailwindcss:
specifier: ^3.4.16
- version: 3.4.16
+ version: 3.4.16(ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2))
vite:
specifier: ^6.0.3
- version: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)
+ version: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(tsx@4.19.2)(yaml@2.6.1)
devDependencies:
'@eslint/js':
specifier: ^9.15.0
@@ -57,12 +75,18 @@ importers:
'@types/node':
specifier: ^22.10.1
version: 22.10.1
+ '@types/nodemailer':
+ specifier: ^6.4.14
+ version: 6.4.17
'@types/react':
specifier: ^19.0.1
version: 19.0.1
'@types/react-dom':
specifier: ^19.0.2
version: 19.0.2(@types/react@19.0.1)
+ concurrently:
+ specifier: ^8.2.2
+ version: 8.2.2
eslint:
specifier: ^9.15.0
version: 9.16.0(jiti@1.21.6)
@@ -75,6 +99,12 @@ importers:
globals:
specifier: ^15.13.0
version: 15.13.0
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@22.10.1)(typescript@5.7.2)
+ tsx:
+ specifier: ^4.19.2
+ version: 4.19.2
typescript:
specifier: ~5.7.2
version: 5.7.2
@@ -159,6 +189,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/runtime@7.26.0':
+ resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.25.9':
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
engines: {node: '>=6.9.0'}
@@ -171,144 +205,292 @@ packages:
resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
engines: {node: '>=6.9.0'}
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@esbuild/aix-ppc64@0.23.1':
+ resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/aix-ppc64@0.24.0':
resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.23.1':
+ resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.24.0':
resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.23.1':
+ resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.24.0':
resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.23.1':
+ resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.24.0':
resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.23.1':
+ resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.24.0':
resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.23.1':
+ resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.24.0':
resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.23.1':
+ resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.24.0':
resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.23.1':
+ resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.24.0':
resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.23.1':
+ resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.24.0':
resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.23.1':
+ resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.24.0':
resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.23.1':
+ resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.24.0':
resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.23.1':
+ resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.24.0':
resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.23.1':
+ resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.24.0':
resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.23.1':
+ resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.24.0':
resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.23.1':
+ resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.24.0':
resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.23.1':
+ resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.24.0':
resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.23.1':
+ resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.24.0':
resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
+ '@esbuild/netbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.24.0':
resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
+ '@esbuild/openbsd-arm64@0.23.1':
+ resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-arm64@0.24.0':
resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.24.0':
resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+ '@esbuild/sunos-x64@0.23.1':
+ resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.24.0':
resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
+ '@esbuild/win32-arm64@0.23.1':
+ resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-arm64@0.24.0':
resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-ia32@0.23.1':
+ resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-ia32@0.24.0':
resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.23.1':
+ resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.24.0':
resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
engines: {node: '>=18'}
@@ -391,6 +573,66 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@next/env@14.2.20':
+ resolution: {integrity: sha512-JfDpuOCB0UBKlEgEy/H6qcBSzHimn/YWjUHzKl1jMeUO+QVRdzmTTl8gFJaNO87c8DXmVKhFCtwxQ9acqB3+Pw==}
+
+ '@next/swc-darwin-arm64@14.2.20':
+ resolution: {integrity: sha512-WDfq7bmROa5cIlk6ZNonNdVhKmbCv38XteVFYsxea1vDJt3SnYGgxLGMTXQNfs5OkFvAhmfKKrwe7Y0Hs+rWOg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@14.2.20':
+ resolution: {integrity: sha512-XIQlC+NAmJPfa2hruLvr1H1QJJeqOTDV+v7tl/jIdoFvqhoihvSNykLU/G6NMgoeo+e/H7p/VeWSOvMUHKtTIg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@14.2.20':
+ resolution: {integrity: sha512-pnzBrHTPXIMm5QX3QC8XeMkpVuoAYOmyfsO4VlPn+0NrHraNuWjdhe+3xLq01xR++iCvX+uoeZmJDKcOxI201Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@14.2.20':
+ resolution: {integrity: sha512-WhJJAFpi6yqmUx1momewSdcm/iRXFQS0HU2qlUGlGE/+98eu7JWLD5AAaP/tkK1mudS/rH2f9E3WCEF2iYDydQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@14.2.20':
+ resolution: {integrity: sha512-ao5HCbw9+iG1Kxm8XsGa3X174Ahn17mSYBQlY6VGsdsYDAbz/ZP13wSLfvlYoIDn1Ger6uYA+yt/3Y9KTIupRg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@14.2.20':
+ resolution: {integrity: sha512-CXm/kpnltKTT7945np6Td3w7shj/92TMRPyI/VvveFe8+YE+/YOJ5hyAWK5rpx711XO1jBCgXl211TWaxOtkaA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@14.2.20':
+ resolution: {integrity: sha512-upJn2HGQgKNDbXVfIgmqT2BN8f3z/mX8ddoyi1I565FHbfowVK5pnMEwauvLvaJf4iijvuKq3kw/b6E9oIVRWA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-ia32-msvc@14.2.20':
+ resolution: {integrity: sha512-igQW/JWciTGJwj3G1ipalD2V20Xfx3ywQy17IV0ciOUBbFhNfyU1DILWsTi32c8KmqgIDviUEulW/yPb2FF90w==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@14.2.20':
+ resolution: {integrity: sha512-AFmqeLW6LtxeFTuoB+MXFeM5fm5052i3MU6xD0WzJDOwku6SkZaxb1bxjBaRC8uNqTRTSPl0yMFtjNowIVI67w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -502,6 +744,24 @@ packages:
cpu: [x64]
os: [win32]
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/helpers@0.5.5':
+ resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+
+ '@tsconfig/node10@1.0.11':
+ resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -514,6 +774,12 @@ packages:
'@types/babel__traverse@7.20.6':
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
+ '@types/body-parser@1.19.5':
+ resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
+
+ '@types/connect@3.4.38':
+ resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@@ -535,18 +801,39 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/express-serve-static-core@5.0.2':
+ resolution: {integrity: sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==}
+
+ '@types/express@5.0.0':
+ resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==}
+
'@types/geojson@7946.0.14':
resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
'@types/history@4.7.11':
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
+ '@types/http-errors@2.0.4':
+ resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
+
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ '@types/mime@1.3.5':
+ resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+
'@types/node@22.10.1':
resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
+ '@types/nodemailer@6.4.17':
+ resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==}
+
+ '@types/qs@6.9.17':
+ resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
+
+ '@types/range-parser@1.2.7':
+ resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+
'@types/react-dom@19.0.2':
resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==}
peerDependencies:
@@ -564,6 +851,12 @@ packages:
'@types/react@19.0.1':
resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==}
+ '@types/send@0.17.4':
+ resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
+
+ '@types/serve-static@1.15.7':
+ resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
+
'@typescript-eslint/eslint-plugin@8.18.0':
resolution: {integrity: sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -611,17 +904,28 @@ packages:
resolution: {integrity: sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@upstash/redis@1.34.3':
+ resolution: {integrity: sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==}
+
'@vitejs/plugin-react@4.3.4':
resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+ acorn-walk@8.3.4:
+ resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+ engines: {node: '>=0.4.0'}
+
acorn@8.14.0:
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
engines: {node: '>=0.4.0'}
@@ -653,12 +957,18 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ array-flatten@1.1.1:
+ resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+
autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14}
@@ -673,6 +983,10 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ body-parser@1.20.3:
+ resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -688,6 +1002,22 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ busboy@1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ call-bind-apply-helpers@1.0.1:
+ resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -707,6 +1037,13 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -724,17 +1061,43 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ concurrently@8.2.2:
+ resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
+ engines: {node: ^14.13.0 || >=16.0.0}
+ hasBin: true
+
+ content-disposition@0.5.4:
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+ engines: {node: '>= 0.6'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie-signature@1.0.6:
+ resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+
+ cookie@0.7.1:
+ resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
+ engines: {node: '>= 0.6'}
+
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ crypto-js@4.2.0:
+ resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -789,6 +1152,18 @@ packages:
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
engines: {node: '>=12'}
+ date-fns@2.30.0:
+ resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
+ engines: {node: '>=0.11'}
+
+ debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
@@ -810,15 +1185,42 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ destroy@1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ diff@4.0.2:
+ resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+ engines: {node: '>=0.3.1'}
+
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dotenv@16.4.7:
+ resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.0:
+ resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==}
+ engines: {node: '>= 0.4'}
+
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
electron-to-chromium@1.5.67:
resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==}
@@ -828,6 +1230,27 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ encodeurl@1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.23.1:
+ resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
esbuild@0.24.0:
resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
engines: {node: '>=18'}
@@ -837,6 +1260,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -894,6 +1320,14 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ express@4.21.2:
+ resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
+ engines: {node: '>= 0.10.0'}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -918,6 +1352,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
+ finalhandler@1.3.1:
+ resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
+ engines: {node: '>= 0.8'}
+
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -933,6 +1371,10 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@@ -950,6 +1392,10 @@ packages:
react-dom:
optional: true
+ fresh@0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -962,6 +1408,17 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.2.5:
+ resolution: {integrity: sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.8.1:
+ resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==}
+
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -986,6 +1443,13 @@ packages:
resolution: {integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==}
engines: {node: '>=18'}
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
@@ -993,10 +1457,25 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1009,10 +1488,17 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -1094,6 +1580,9 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -1104,14 +1593,41 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+
+ merge-descriptors@1.0.3:
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ methods@1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime@1.6.0:
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1129,6 +1645,9 @@ packages:
motion-utils@11.13.0:
resolution: {integrity: sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A==}
+ ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1143,9 +1662,35 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
+ next@14.2.20:
+ resolution: {integrity: sha512-yPvIiWsiyVYqJlSQxwmzMIReXn5HxFNq4+tlVQ812N1FbvhmE+fDpIAD7bcS2mGYQwPJ5vAsQouyme2eKsxaug==}
+ engines: {node: '>=18.17.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.41.2
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ sass:
+ optional: true
+
node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
+ nodemailer@6.9.16:
+ resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==}
+ engines: {node: '>=6.0.0'}
+
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -1162,6 +1707,14 @@ packages:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
+ object-inspect@1.13.3:
+ resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
+ engines: {node: '>= 0.4'}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -1181,6 +1734,10 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -1196,6 +1753,9 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
+ path-to-regexp@0.1.12:
+ resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
+
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -1248,6 +1808,10 @@ packages:
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
postcss@8.4.49:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14}
@@ -1259,13 +1823,29 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ qs@6.13.0:
+ resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+ engines: {node: '>=0.6'}
+
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+
react-dom@19.0.0:
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
peerDependencies:
@@ -1327,10 +1907,20 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
+ regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
@@ -1347,6 +1937,15 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ rxjs@7.8.1:
+ resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
scheduler@0.25.0:
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
@@ -1359,9 +1958,24 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ send@0.19.0:
+ resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
+ engines: {node: '>= 0.8.0'}
+
+ serve-static@1.16.2:
+ resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
+ engines: {node: '>= 0.8.0'}
+
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -1370,6 +1984,14 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.8.2:
+ resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.0.6:
+ resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+ engines: {node: '>= 0.4'}
+
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@@ -1378,6 +2000,17 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ spawn-command@0.0.2:
+ resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
+
+ statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+
+ streamsearch@1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -1398,6 +2031,19 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ styled-jsx@5.1.1:
+ resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -1407,6 +2053,10 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@@ -1427,10 +2077,18 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
topojson-client@3.1.0:
resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==}
hasBin: true
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
ts-api-utils@1.4.3:
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
engines: {node: '>=16'}
@@ -1440,9 +2098,28 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@4.19.2:
+ resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
turbo-stream@2.4.0:
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
@@ -1450,6 +2127,10 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
+ type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+
typescript-eslint@8.18.0:
resolution: {integrity: sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1465,6 +2146,10 @@ packages:
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
update-browserslist-db@1.1.1:
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
hasBin: true
@@ -1477,6 +2162,17 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ utils-merge@1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
vite@6.0.3:
resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -1534,6 +2230,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -1542,6 +2242,18 @@ packages:
engines: {node: '>= 14'}
hasBin: true
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -1642,6 +2354,10 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.25.9
+ '@babel/runtime@7.26.0':
+ dependencies:
+ regenerator-runtime: 0.14.1
+
'@babel/template@7.25.9':
dependencies:
'@babel/code-frame': 7.26.2
@@ -1665,75 +2381,151 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@esbuild/aix-ppc64@0.23.1':
+ optional: true
+
'@esbuild/aix-ppc64@0.24.0':
optional: true
+ '@esbuild/android-arm64@0.23.1':
+ optional: true
+
'@esbuild/android-arm64@0.24.0':
optional: true
+ '@esbuild/android-arm@0.23.1':
+ optional: true
+
'@esbuild/android-arm@0.24.0':
optional: true
+ '@esbuild/android-x64@0.23.1':
+ optional: true
+
'@esbuild/android-x64@0.24.0':
optional: true
+ '@esbuild/darwin-arm64@0.23.1':
+ optional: true
+
'@esbuild/darwin-arm64@0.24.0':
optional: true
+ '@esbuild/darwin-x64@0.23.1':
+ optional: true
+
'@esbuild/darwin-x64@0.24.0':
optional: true
+ '@esbuild/freebsd-arm64@0.23.1':
+ optional: true
+
'@esbuild/freebsd-arm64@0.24.0':
optional: true
+ '@esbuild/freebsd-x64@0.23.1':
+ optional: true
+
'@esbuild/freebsd-x64@0.24.0':
optional: true
+ '@esbuild/linux-arm64@0.23.1':
+ optional: true
+
'@esbuild/linux-arm64@0.24.0':
optional: true
+ '@esbuild/linux-arm@0.23.1':
+ optional: true
+
'@esbuild/linux-arm@0.24.0':
optional: true
+ '@esbuild/linux-ia32@0.23.1':
+ optional: true
+
'@esbuild/linux-ia32@0.24.0':
optional: true
+ '@esbuild/linux-loong64@0.23.1':
+ optional: true
+
'@esbuild/linux-loong64@0.24.0':
optional: true
+ '@esbuild/linux-mips64el@0.23.1':
+ optional: true
+
'@esbuild/linux-mips64el@0.24.0':
optional: true
+ '@esbuild/linux-ppc64@0.23.1':
+ optional: true
+
'@esbuild/linux-ppc64@0.24.0':
optional: true
+ '@esbuild/linux-riscv64@0.23.1':
+ optional: true
+
'@esbuild/linux-riscv64@0.24.0':
optional: true
+ '@esbuild/linux-s390x@0.23.1':
+ optional: true
+
'@esbuild/linux-s390x@0.24.0':
optional: true
+ '@esbuild/linux-x64@0.23.1':
+ optional: true
+
'@esbuild/linux-x64@0.24.0':
optional: true
+ '@esbuild/netbsd-x64@0.23.1':
+ optional: true
+
'@esbuild/netbsd-x64@0.24.0':
optional: true
+ '@esbuild/openbsd-arm64@0.23.1':
+ optional: true
+
'@esbuild/openbsd-arm64@0.24.0':
optional: true
+ '@esbuild/openbsd-x64@0.23.1':
+ optional: true
+
'@esbuild/openbsd-x64@0.24.0':
optional: true
+ '@esbuild/sunos-x64@0.23.1':
+ optional: true
+
'@esbuild/sunos-x64@0.24.0':
optional: true
+ '@esbuild/win32-arm64@0.23.1':
+ optional: true
+
'@esbuild/win32-arm64@0.24.0':
optional: true
+ '@esbuild/win32-ia32@0.23.1':
+ optional: true
+
'@esbuild/win32-ia32@0.24.0':
optional: true
+ '@esbuild/win32-x64@0.23.1':
+ optional: true
+
'@esbuild/win32-x64@0.24.0':
optional: true
@@ -1815,6 +2607,40 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@next/env@14.2.20': {}
+
+ '@next/swc-darwin-arm64@14.2.20':
+ optional: true
+
+ '@next/swc-darwin-x64@14.2.20':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@14.2.20':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@14.2.20':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@14.2.20':
+ optional: true
+
+ '@next/swc-linux-x64-musl@14.2.20':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@14.2.20':
+ optional: true
+
+ '@next/swc-win32-ia32-msvc@14.2.20':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@14.2.20':
+ optional: true
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -1887,6 +2713,21 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.28.1':
optional: true
+ '@swc/counter@0.1.3': {}
+
+ '@swc/helpers@0.5.5':
+ dependencies:
+ '@swc/counter': 0.1.3
+ tslib: 2.8.1
+
+ '@tsconfig/node10@1.0.11': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
+
+ '@tsconfig/node16@1.0.4': {}
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.26.2
@@ -1908,6 +2749,15 @@ snapshots:
dependencies:
'@babel/types': 7.26.0
+ '@types/body-parser@1.19.5':
+ dependencies:
+ '@types/connect': 3.4.38
+ '@types/node': 22.10.1
+
+ '@types/connect@3.4.38':
+ dependencies:
+ '@types/node': 22.10.1
+
'@types/cookie@0.6.0': {}
'@types/d3-color@2.0.6': {}
@@ -1929,16 +2779,42 @@ snapshots:
'@types/estree@1.0.6': {}
+ '@types/express-serve-static-core@5.0.2':
+ dependencies:
+ '@types/node': 22.10.1
+ '@types/qs': 6.9.17
+ '@types/range-parser': 1.2.7
+ '@types/send': 0.17.4
+
+ '@types/express@5.0.0':
+ dependencies:
+ '@types/body-parser': 1.19.5
+ '@types/express-serve-static-core': 5.0.2
+ '@types/qs': 6.9.17
+ '@types/serve-static': 1.15.7
+
'@types/geojson@7946.0.14': {}
'@types/history@4.7.11': {}
+ '@types/http-errors@2.0.4': {}
+
'@types/json-schema@7.0.15': {}
+ '@types/mime@1.3.5': {}
+
'@types/node@22.10.1':
dependencies:
undici-types: 6.20.0
+ '@types/nodemailer@6.4.17':
+ dependencies:
+ '@types/node': 22.10.1
+
+ '@types/qs@6.9.17': {}
+
+ '@types/range-parser@1.2.7': {}
+
'@types/react-dom@19.0.2(@types/react@19.0.1)':
dependencies:
'@types/react': 19.0.1
@@ -1965,6 +2841,17 @@ snapshots:
dependencies:
csstype: 3.1.3
+ '@types/send@0.17.4':
+ dependencies:
+ '@types/mime': 1.3.5
+ '@types/node': 22.10.1
+
+ '@types/serve-static@1.15.7':
+ dependencies:
+ '@types/http-errors': 2.0.4
+ '@types/node': 22.10.1
+ '@types/send': 0.17.4
+
'@typescript-eslint/eslint-plugin@8.18.0(@typescript-eslint/parser@8.18.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -2042,21 +2929,34 @@ snapshots:
'@typescript-eslint/types': 8.18.0
eslint-visitor-keys: 4.2.0
- '@vitejs/plugin-react@4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1))':
+ '@upstash/redis@1.34.3':
+ dependencies:
+ crypto-js: 4.2.0
+
+ '@vitejs/plugin-react@4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(tsx@4.19.2)(yaml@2.6.1))':
dependencies:
'@babel/core': 7.26.0
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
- vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1)
+ vite: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(tsx@4.19.2)(yaml@2.6.1)
transitivePeerDependencies:
- supports-color
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0
+ acorn-walk@8.3.4:
+ dependencies:
+ acorn: 8.14.0
+
acorn@8.14.0: {}
ajv@6.12.6:
@@ -2083,10 +2983,14 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.1
+ arg@4.1.3: {}
+
arg@5.0.2: {}
argparse@2.0.1: {}
+ array-flatten@1.1.1: {}
+
autoprefixer@10.4.20(postcss@8.4.49):
dependencies:
browserslist: 4.24.2
@@ -2101,6 +3005,23 @@ snapshots:
binary-extensions@2.3.0: {}
+ body-parser@1.20.3:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.13.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@@ -2121,6 +3042,24 @@ snapshots:
node-releases: 2.0.18
update-browserslist-db: 1.1.1(browserslist@4.24.2)
+ busboy@1.6.0:
+ dependencies:
+ streamsearch: 1.1.0
+
+ bytes@3.1.2: {}
+
+ call-bind-apply-helpers@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.1
+ es-define-property: 1.0.1
+ get-intrinsic: 1.2.5
+ set-function-length: 1.2.2
+
callsites@3.1.0: {}
camelcase-css@2.0.1: {}
@@ -2144,6 +3083,14 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ client-only@0.0.1: {}
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -2156,16 +3103,42 @@ snapshots:
concat-map@0.0.1: {}
+ concurrently@8.2.2:
+ dependencies:
+ chalk: 4.1.2
+ date-fns: 2.30.0
+ lodash: 4.17.21
+ rxjs: 7.8.1
+ shell-quote: 1.8.2
+ spawn-command: 0.0.2
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+
+ content-disposition@0.5.4:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ content-type@1.0.5: {}
+
convert-source-map@2.0.0: {}
+ cookie-signature@1.0.6: {}
+
+ cookie@0.7.1: {}
+
cookie@1.0.2: {}
+ create-require@1.1.1: {}
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
+ crypto-js@4.2.0: {}
+
cssesc@3.0.0: {}
csstype@3.1.3: {}
@@ -2214,6 +3187,14 @@ snapshots:
d3-selection: 3.0.0
d3-transition: 3.0.1(d3-selection@3.0.0)
+ date-fns@2.30.0:
+ dependencies:
+ '@babel/runtime': 7.26.0
+
+ debug@2.6.9:
+ dependencies:
+ ms: 2.0.0
+
debug@4.3.7:
dependencies:
ms: 2.1.3
@@ -2224,18 +3205,75 @@ snapshots:
deep-is@0.1.4: {}
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ depd@2.0.0: {}
+
+ destroy@1.2.0: {}
+
didyoumean@1.2.2: {}
+ diff@4.0.2: {}
+
dlv@1.1.3: {}
+ dotenv@16.4.7: {}
+
+ dunder-proto@1.0.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
eastasianwidth@0.2.0: {}
+ ee-first@1.1.1: {}
+
electron-to-chromium@1.5.67: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
+ encodeurl@1.0.2: {}
+
+ encodeurl@2.0.0: {}
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ esbuild@0.23.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.23.1
+ '@esbuild/android-arm': 0.23.1
+ '@esbuild/android-arm64': 0.23.1
+ '@esbuild/android-x64': 0.23.1
+ '@esbuild/darwin-arm64': 0.23.1
+ '@esbuild/darwin-x64': 0.23.1
+ '@esbuild/freebsd-arm64': 0.23.1
+ '@esbuild/freebsd-x64': 0.23.1
+ '@esbuild/linux-arm': 0.23.1
+ '@esbuild/linux-arm64': 0.23.1
+ '@esbuild/linux-ia32': 0.23.1
+ '@esbuild/linux-loong64': 0.23.1
+ '@esbuild/linux-mips64el': 0.23.1
+ '@esbuild/linux-ppc64': 0.23.1
+ '@esbuild/linux-riscv64': 0.23.1
+ '@esbuild/linux-s390x': 0.23.1
+ '@esbuild/linux-x64': 0.23.1
+ '@esbuild/netbsd-x64': 0.23.1
+ '@esbuild/openbsd-arm64': 0.23.1
+ '@esbuild/openbsd-x64': 0.23.1
+ '@esbuild/sunos-x64': 0.23.1
+ '@esbuild/win32-arm64': 0.23.1
+ '@esbuild/win32-ia32': 0.23.1
+ '@esbuild/win32-x64': 0.23.1
+
esbuild@0.24.0:
optionalDependencies:
'@esbuild/aix-ppc64': 0.24.0
@@ -2265,6 +3303,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-html@1.0.3: {}
+
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@5.1.0(eslint@9.16.0(jiti@1.21.6)):
@@ -2343,6 +3383,44 @@ snapshots:
esutils@2.0.3: {}
+ etag@1.8.1: {}
+
+ express@4.21.2:
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.3
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookie: 0.7.1
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.3.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ merge-descriptors: 1.0.3
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.12
+ proxy-addr: 2.0.7
+ qs: 6.13.0
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.19.0
+ serve-static: 1.16.2
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
fast-deep-equal@3.1.3: {}
fast-glob@3.3.2:
@@ -2369,6 +3447,18 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
+ finalhandler@1.3.1:
+ dependencies:
+ debug: 2.6.9
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.1
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -2386,6 +3476,8 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ forwarded@0.2.0: {}
+
fraction.js@4.3.7: {}
framer-motion@11.13.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
@@ -2397,6 +3489,8 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
+ fresh@0.5.2: {}
+
fsevents@2.3.3:
optional: true
@@ -2404,6 +3498,23 @@ snapshots:
gensync@1.0.0-beta.2: {}
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.2.5:
+ dependencies:
+ call-bind-apply-helpers: 1.0.1
+ dunder-proto: 1.0.0
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+
+ get-tsconfig@4.8.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -2427,14 +3538,36 @@ snapshots:
globals@15.13.0: {}
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
graphemer@1.4.0: {}
has-flag@4.0.0: {}
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-symbols@1.1.0: {}
+
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
+ http-errors@2.0.0:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+
ignore@5.3.2: {}
import-fresh@3.3.0:
@@ -2444,8 +3577,12 @@ snapshots:
imurmurhash@0.1.4: {}
+ inherits@2.0.4: {}
+
internmap@2.0.3: {}
+ ipaddr.js@1.9.1: {}
+
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@@ -2509,6 +3646,8 @@ snapshots:
lodash.merge@4.6.2: {}
+ lodash@4.17.21: {}
+
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
@@ -2519,13 +3658,29 @@ snapshots:
dependencies:
yallist: 3.1.1
+ make-error@1.3.6: {}
+
+ media-typer@0.3.0: {}
+
+ merge-descriptors@1.0.3: {}
+
merge2@1.4.1: {}
+ methods@1.1.2: {}
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime@1.6.0: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -2540,6 +3695,8 @@ snapshots:
motion-utils@11.13.0: {}
+ ms@2.0.0: {}
+
ms@2.1.3: {}
mz@2.7.0:
@@ -2552,8 +3709,37 @@ snapshots:
natural-compare@1.4.0: {}
+ negotiator@0.6.3: {}
+
+ next@14.2.20(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@next/env': 14.2.20
+ '@swc/helpers': 0.5.5
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001684
+ graceful-fs: 4.2.11
+ postcss: 8.4.31
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ styled-jsx: 5.1.1(@babel/core@7.26.0)(react@19.0.0)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 14.2.20
+ '@next/swc-darwin-x64': 14.2.20
+ '@next/swc-linux-arm64-gnu': 14.2.20
+ '@next/swc-linux-arm64-musl': 14.2.20
+ '@next/swc-linux-x64-gnu': 14.2.20
+ '@next/swc-linux-x64-musl': 14.2.20
+ '@next/swc-win32-arm64-msvc': 14.2.20
+ '@next/swc-win32-ia32-msvc': 14.2.20
+ '@next/swc-win32-x64-msvc': 14.2.20
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
node-releases@2.0.18: {}
+ nodemailer@6.9.16: {}
+
normalize-path@3.0.0: {}
normalize-range@0.1.2: {}
@@ -2562,6 +3748,12 @@ snapshots:
object-hash@3.0.0: {}
+ object-inspect@1.13.3: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -2585,6 +3777,8 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parseurl@1.3.3: {}
+
path-exists@4.0.0: {}
path-key@3.1.1: {}
@@ -2596,6 +3790,8 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
+ path-to-regexp@0.1.12: {}
+
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -2616,12 +3812,13 @@ snapshots:
camelcase-css: 2.0.1
postcss: 8.4.49
- postcss-load-config@4.0.2(postcss@8.4.49):
+ postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2)):
dependencies:
lilconfig: 3.1.3
yaml: 2.6.1
optionalDependencies:
postcss: 8.4.49
+ ts-node: 10.9.2(@types/node@22.10.1)(typescript@5.7.2)
postcss-nested@6.2.0(postcss@8.4.49):
dependencies:
@@ -2635,6 +3832,12 @@ snapshots:
postcss-value-parser@4.2.0: {}
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.8
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
postcss@8.4.49:
dependencies:
nanoid: 3.3.8
@@ -2649,10 +3852,28 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
punycode@2.3.1: {}
+ qs@6.13.0:
+ dependencies:
+ side-channel: 1.0.6
+
queue-microtask@1.2.3: {}
+ range-parser@1.2.1: {}
+
+ raw-body@2.5.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+
react-dom@19.0.0(react@19.0.0):
dependencies:
react: 19.0.0
@@ -2708,8 +3929,14 @@ snapshots:
dependencies:
picomatch: 2.3.1
+ regenerator-runtime@0.14.1: {}
+
+ require-directory@2.1.1: {}
+
resolve-from@4.0.0: {}
+ resolve-pkg-maps@1.0.0: {}
+
resolve@1.22.8:
dependencies:
is-core-module: 2.15.1
@@ -2747,24 +3974,85 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ rxjs@7.8.1:
+ dependencies:
+ tslib: 2.8.1
+
+ safe-buffer@5.2.1: {}
+
+ safer-buffer@2.1.2: {}
+
scheduler@0.25.0: {}
semver@6.3.1: {}
semver@7.6.3: {}
+ send@0.19.0:
+ dependencies:
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ mime: 1.6.0
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@1.16.2:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 0.19.0
+ transitivePeerDependencies:
+ - supports-color
+
set-cookie-parser@2.7.1: {}
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.5
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ setprototypeof@1.2.0: {}
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
+ shell-quote@1.8.2: {}
+
+ side-channel@1.0.6:
+ dependencies:
+ call-bind: 1.0.8
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.5
+ object-inspect: 1.13.3
+
signal-exit@4.1.0: {}
source-map-js@1.2.1: {}
+ spawn-command@0.0.2: {}
+
+ statuses@2.0.1: {}
+
+ streamsearch@1.1.0: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -2787,6 +4075,13 @@ snapshots:
strip-json-comments@3.1.1: {}
+ styled-jsx@5.1.1(@babel/core@7.26.0)(react@19.0.0):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.0.0
+ optionalDependencies:
+ '@babel/core': 7.26.0
+
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.5
@@ -2801,9 +4096,13 @@ snapshots:
dependencies:
has-flag: 4.0.0
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
supports-preserve-symlinks-flag@1.0.0: {}
- tailwindcss@3.4.16:
+ tailwindcss@3.4.16(ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -2822,7 +4121,7 @@ snapshots:
postcss: 8.4.49
postcss-import: 15.1.0(postcss@8.4.49)
postcss-js: 4.0.1(postcss@8.4.49)
- postcss-load-config: 4.0.2(postcss@8.4.49)
+ postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2))
postcss-nested: 6.2.0(postcss@8.4.49)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -2842,24 +4141,58 @@ snapshots:
dependencies:
is-number: 7.0.0
+ toidentifier@1.0.1: {}
+
topojson-client@3.1.0:
dependencies:
commander: 2.20.3
+ tree-kill@1.2.2: {}
+
ts-api-utils@1.4.3(typescript@5.7.2):
dependencies:
typescript: 5.7.2
ts-interface-checker@0.1.13: {}
+ ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 22.10.1
+ acorn: 8.14.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 5.7.2
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
tslib@2.8.1: {}
+ tsx@4.19.2:
+ dependencies:
+ esbuild: 0.23.1
+ get-tsconfig: 4.8.1
+ optionalDependencies:
+ fsevents: 2.3.3
+
turbo-stream@2.4.0: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
+ type-is@1.6.18:
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+
typescript-eslint@8.18.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.18.0(@typescript-eslint/parser@8.18.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
@@ -2874,6 +4207,8 @@ snapshots:
undici-types@6.20.0: {}
+ unpipe@1.0.0: {}
+
update-browserslist-db@1.1.1(browserslist@4.24.2):
dependencies:
browserslist: 4.24.2
@@ -2886,7 +4221,13 @@ snapshots:
util-deprecate@1.0.2: {}
- vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1):
+ utils-merge@1.0.1: {}
+
+ v8-compile-cache-lib@3.0.1: {}
+
+ vary@1.1.2: {}
+
+ vite@6.0.3(@types/node@22.10.1)(jiti@1.21.6)(tsx@4.19.2)(yaml@2.6.1):
dependencies:
esbuild: 0.24.0
postcss: 8.4.49
@@ -2895,6 +4236,7 @@ snapshots:
'@types/node': 22.10.1
fsevents: 2.3.3
jiti: 1.21.6
+ tsx: 4.19.2
yaml: 2.6.1
which@2.0.2:
@@ -2915,8 +4257,24 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
+ y18n@5.0.8: {}
+
yallist@3.1.1: {}
yaml@2.6.1: {}
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yn@3.1.1: {}
+
yocto-queue@0.1.0: {}
diff --git a/server.ts b/server.ts
new file mode 100644
index 0000000..cb00368
--- /dev/null
+++ b/server.ts
@@ -0,0 +1,44 @@
+import express from 'express'
+import dotenv from 'dotenv'
+import { POST as handleContact } from './src/api/contact.js'
+import { fileURLToPath } from 'url'
+import { dirname, join } from 'path'
+
+dotenv.config()
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = dirname(__filename)
+
+const app = express()
+app.use(express.json())
+
+app.use((req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*')
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
+ res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
+ next()
+})
+
+app.use(express.static(join(__dirname, 'dist')))
+
+app.post('/api/contact', async (req, res) => {
+ try {
+ const response = await handleContact(req)
+ res.status(response.status).json(response.body)
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ message: 'Internal server error'
+ })
+ }
+})
+
+app.get('*', (req, res) => {
+ res.sendFile(join(__dirname, 'dist', 'index.html'))
+})
+
+const PORT = process.env.PORT || 804
+
+app.listen(PORT, () => {
+ console.info(`Server running on port ${PORT}`)
+})
diff --git a/src/App.tsx b/src/App.tsx
index fe5e4f2..815557f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,20 +5,24 @@ import Services from './pages/Services'
import About from './pages/About'
import Contact from './pages/Contact'
import Resources from './pages/Resources'
+import { NotificationProvider } from './context/NotificationProvider'
function App() {
return (
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
)
}
-export default App
\ No newline at end of file
+
+export default App
diff --git a/src/api/contact.ts b/src/api/contact.ts
new file mode 100644
index 0000000..788a0b6
--- /dev/null
+++ b/src/api/contact.ts
@@ -0,0 +1,106 @@
+import { Request } from 'express'
+import { rateLimit } from '../lib/rate-limit.js'
+import { sendEmail } from '../lib/email.js'
+
+interface FormData {
+ name: string
+ email: string
+ service: string
+ message: string
+}
+
+const validateEmail = (email: string): boolean => {
+ const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+ return pattern.test(email)
+}
+
+const validateInput = (data: FormData): { isValid: boolean; error?: string } => {
+ if (!data.name || data.name.length < 2 || data.name.length > 50) {
+ return { isValid: false, error: 'Invalid name length' }
+ }
+
+ if (!data.email || !validateEmail(data.email)) {
+ return { isValid: false, error: 'Invalid email format' }
+ }
+
+ if (!data.service) {
+ return { isValid: false, error: 'Service selection required' }
+ }
+
+ if (!data.message || data.message.length < 10 || data.message.length > 1000) {
+ return { isValid: false, error: 'Invalid message length' }
+ }
+
+ return { isValid: true }
+}
+
+const sanitizeInput = (input: string): string => {
+ return input
+ .trim()
+ .replace(/[<>]/g, '')
+ .slice(0, 1000)
+}
+
+export async function POST(req: Request) {
+ try {
+ if (!req.body) {
+ return {
+ status: 400,
+ body: {
+ success: false,
+ message: 'Request body is missing'
+ }
+ }
+ }
+
+ const rateLimitResult = await rateLimit(req)
+ if (!rateLimitResult.success) {
+ return {
+ status: 429,
+ body: {
+ success: false,
+ message: 'Too many requests. Please try again later.'
+ }
+ }
+ }
+
+ const data: FormData = req.body
+ const validation = validateInput(data)
+
+ if (!validation.isValid) {
+ return {
+ status: 400,
+ body: {
+ success: false,
+ message: validation.error
+ }
+ }
+ }
+
+ const sanitizedData = {
+ name: sanitizeInput(data.name),
+ email: sanitizeInput(data.email),
+ service: sanitizeInput(data.service),
+ message: sanitizeInput(data.message)
+ }
+
+ await sendEmail(sanitizedData)
+
+ return {
+ status: 200,
+ body: {
+ success: true,
+ message: 'Message sent successfully'
+ }
+ }
+
+ } catch (error) {
+ return {
+ status: 500,
+ body: {
+ success: false,
+ message: 'Failed to send message. Please try again later.'
+ }
+ }
+ }
+}
diff --git a/src/api/route.ts b/src/api/route.ts
new file mode 100644
index 0000000..7f4c4b1
--- /dev/null
+++ b/src/api/route.ts
@@ -0,0 +1,113 @@
+import { Request, Response } from 'express'
+import nodemailer from 'nodemailer'
+import { rateLimit } from '../lib/rate-limit'
+
+interface FormData {
+ name: string
+ email: string
+ service: string
+ message: string
+}
+
+const validateEmail = (email: string): boolean => {
+ const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+ return pattern.test(email)
+}
+
+const validateInput = (data: FormData): { isValid: boolean; error?: string } => {
+ if (!data.name || data.name.length < 2 || data.name.length > 50) {
+ return { isValid: false, error: 'Invalid name length' }
+ }
+
+ if (!data.email || !validateEmail(data.email)) {
+ return { isValid: false, error: 'Invalid email format' }
+ }
+
+ if (!data.service) {
+ return { isValid: false, error: 'Service selection required' }
+ }
+
+ if (!data.message || data.message.length < 10 || data.message.length > 1000) {
+ return { isValid: false, error: 'Invalid message length' }
+ }
+
+ return { isValid: true }
+}
+
+const sanitizeInput = (input: string): string => {
+ return input
+ .trim()
+ .replace(/[<>]/g, '')
+ .slice(0, 1000)
+}
+
+export async function POST(req: Request, res: Response) {
+ try {
+ const rateLimitResult = await rateLimit(req)
+ if (!rateLimitResult.success) {
+ return res.status(429).json({
+ success: false,
+ message: 'Too many requests. Please try again later.'
+ })
+ }
+
+ const data: FormData = req.body
+
+ const validation = validateInput(data)
+ if (!validation.isValid) {
+ return res.status(400).json({
+ success: false,
+ message: validation.error
+ })
+ }
+
+ const sanitizedData = {
+ name: sanitizeInput(data.name),
+ email: sanitizeInput(data.email),
+ service: sanitizeInput(data.service),
+ message: sanitizeInput(data.message)
+ }
+
+ const transporter = nodemailer.createTransport({
+ host: process.env.SMTP_HOST,
+ port: Number(process.env.SMTP_PORT),
+ secure: process.env.SMTP_SECURE === 'true',
+ auth: {
+ user: process.env.GOOGLE_EMAIL,
+ pass: process.env.GOOGLE_APP_PASSWORD,
+ },
+ })
+
+ const mailOptions = {
+ from: process.env.GOOGLE_EMAIL,
+ to: process.env.RECIPIENT_EMAIL,
+ subject: `New Contact Form Submission - ${sanitizedData.service}`,
+ html: `
+
+
New Contact Form Submission
+
+
Name: ${sanitizedData.name}
+
Email: ${sanitizedData.email}
+
Service: ${sanitizedData.service}
+
Message:
+
${sanitizedData.message}
+
+
+ `,
+ }
+
+ await transporter.sendMail(mailOptions)
+
+ return res.status(200).json({
+ success: true,
+ message: 'Email sent successfully'
+ })
+
+ } catch (error) {
+ console.error('Error sending email:', error)
+ return res.status(500).json({
+ success: false,
+ message: 'Failed to send email'
+ })
+ }
+}
diff --git a/src/components/VideoPlayer.tsx b/src/components/VideoPlayer.tsx
index 2b1ea04..c5a3e5a 100644
--- a/src/components/VideoPlayer.tsx
+++ b/src/components/VideoPlayer.tsx
@@ -14,7 +14,7 @@ const VideoPlayer = ({ src, poster, vttSrc }: VideoPlayerProps) => {
const [playbackRate, setPlaybackRate] = useState(1)
const [progress, setProgress] = useState(0)
const videoRef = useRef(null)
- const timeoutRef = useRef>()
+ const timeoutRef = useRef>(null)
const playbackRates = [0.5, 1, 2, 3]
diff --git a/src/components/shared/Toast.tsx b/src/components/shared/Toast.tsx
new file mode 100644
index 0000000..c32e91c
--- /dev/null
+++ b/src/components/shared/Toast.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import { motion, AnimatePresence } from 'framer-motion'
+import { FaTimes } from 'react-icons/fa'
+
+interface ToastProps {
+ message: string
+ type: 'success' | 'error'
+ isVisible: boolean
+ onClose: () => void
+}
+
+export default function Toast({ message, type, isVisible, onClose }: ToastProps) {
+ const bgColor = type === 'success'
+ ? 'from-primary to-secondary'
+ : 'from-red-600 to-red-800'
+
+ return (
+
+ {isVisible && (
+
+
+
+
+
+
+ )}
+
+ )
+}
diff --git a/src/context/NotificationProvider.tsx b/src/context/NotificationProvider.tsx
new file mode 100644
index 0000000..e552c40
--- /dev/null
+++ b/src/context/NotificationProvider.tsx
@@ -0,0 +1,32 @@
+'use client'
+import { useState } from 'react'
+import Toast from "../components/shared/Toast"
+import { NotificationContext } from './notification-context'
+
+export function NotificationProvider({ children }: { children: React.ReactNode }) {
+ const [message, setMessage] = useState('')
+ const [type, setType] = useState<'success' | 'error'>('success')
+ const [isVisible, setIsVisible] = useState(false)
+
+ const showNotification = (message: string, type: 'success' | 'error') => {
+ setMessage(message)
+ setType(type)
+ setIsVisible(true)
+ }
+
+ const hideNotification = () => {
+ setIsVisible(false)
+ }
+
+ return (
+
+ {children}
+
+
+ )
+}
diff --git a/src/context/notification-context.ts b/src/context/notification-context.ts
new file mode 100644
index 0000000..65a59af
--- /dev/null
+++ b/src/context/notification-context.ts
@@ -0,0 +1,13 @@
+import { createContext, useContext } from 'react'
+
+interface NotificationContextType {
+ showNotification: (message: string, type: 'success' | 'error') => void
+ hideNotification: () => void
+}
+
+export const NotificationContext = createContext({
+ showNotification: () => {},
+ hideNotification: () => {}
+})
+
+export const useNotification = () => useContext(NotificationContext)
diff --git a/src/lib/email.ts b/src/lib/email.ts
new file mode 100644
index 0000000..f80cbd1
--- /dev/null
+++ b/src/lib/email.ts
@@ -0,0 +1,41 @@
+import nodemailer from 'nodemailer'
+
+interface EmailData {
+ name: string
+ email: string
+ service: string
+ message: string
+}
+
+export async function sendEmail(data: EmailData) {
+ const transporter = nodemailer.createTransport({
+ host: process.env.SMTP_HOST,
+ port: Number(process.env.SMTP_PORT),
+ secure: process.env.SMTP_SECURE === 'true',
+ auth: {
+ user: process.env.GOOGLE_EMAIL,
+ pass: process.env.GOOGLE_APP_PASSWORD,
+ },
+ })
+
+ const mailOptions = {
+ from: process.env.GOOGLE_EMAIL,
+ to: process.env.RECIPIENT_EMAIL,
+ subject: `New Contact Form Submission - ${data.service}`,
+ html: `
+
+
New Contact Form Submission
+
+
Name: ${data.name}
+
Email: ${data.email}
+
Service: ${data.service}
+
Message:
+
${data.message}
+
+
+ `,
+ }
+
+ const info = await transporter.sendMail(mailOptions)
+ return info
+}
diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts
new file mode 100644
index 0000000..6ad7b60
--- /dev/null
+++ b/src/lib/rate-limit.ts
@@ -0,0 +1,62 @@
+import { Redis } from '@upstash/redis'
+import { Request } from 'express'
+
+export interface RateLimitConfig {
+ interval: number
+ limit: number
+}
+
+export interface RateLimitResult {
+ success: boolean
+ limit: number
+ remaining: number
+ reset: number
+}
+
+export class RateLimit {
+ private redis: Redis
+ private prefix: string
+
+ constructor() {
+ this.redis = new Redis({
+ url: process.env.UPSTASH_REDIS_REST_URL || '',
+ token: process.env.UPSTASH_REDIS_REST_TOKEN || ''
+ })
+ this.prefix = 'ratelimit'
+ }
+
+ private getIP(req: Request): string {
+ return req.ip || req.socket.remoteAddress || 'unknown'
+ }
+
+ private getKey(identifier: string): string {
+ return `${this.prefix}:${identifier}`
+ }
+
+ async check(req: Request, config: RateLimitConfig): Promise {
+ const ip = this.getIP(req)
+ const key = this.getKey(ip)
+ const now = Math.floor(Date.now() / 1000)
+ const windowStart = now - (now % config.interval)
+ const windowKey = `${key}:${windowStart}`
+
+ const pipeline = this.redis.pipeline()
+ pipeline.incr(windowKey)
+ pipeline.expire(windowKey, config.interval)
+
+ const [count] = await pipeline.exec()
+ const remaining = Math.max(0, config.limit - (count as number))
+
+ return {
+ success: (count as number) <= config.limit,
+ limit: config.limit,
+ remaining,
+ reset: windowStart + config.interval
+ }
+ }
+}
+
+export async function rateLimit(req: Request, config: RateLimitConfig = { interval: 60 * 60, limit: 5 }): Promise {
+ const limiter = new RateLimit()
+ return limiter.check(req, config)
+}
diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx
index 781f14a..d70a7b2 100644
--- a/src/pages/Contact.tsx
+++ b/src/pages/Contact.tsx
@@ -1,18 +1,50 @@
import { useState } from 'react'
import { motion } from 'framer-motion'
import { FaEnvelope, FaMapMarkerAlt } from 'react-icons/fa'
+import { useNotification } from '../context/notification-context'
const Contact = () => {
+ const { showNotification } = useNotification()
+ const [isSubmitting, setIsSubmitting] = useState(false)
const [formData, setFormData] = useState({
name: '',
email: '',
- subject: '',
+ service: '',
message: ''
})
- const handleSubmit = (e: React.FormEvent) => {
+ const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
- // Form submission logic will be implemented here
+
+ if (isSubmitting) return
+
+ setIsSubmitting(true)
+
+ try {
+ const response = await fetch('/api/contact', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(formData),
+ })
+
+ const data = await response.json()
+
+ if (data.success) {
+ showNotification('Message sent successfully!', 'success')
+ setFormData({ name: '', email: '', service: '', message: '' })
+ } else {
+ showNotification(data.message || 'Failed to send message', 'error')
+ }
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'An error occurred. Please try again later.'
+ showNotification(errorMessage, 'error')
+ } finally {
+ setTimeout(() => {
+ setIsSubmitting(false)
+ }, 1000)
+ }
}
const contactInfo = [
@@ -34,6 +66,7 @@ const Contact = () => {
Get in Touch
@@ -74,6 +107,8 @@ const Contact = () => {
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-4 py-2 border border-accent-lake rounded-lg focus:outline-none focus:border-primary"
required
+ minLength={2}
+ maxLength={50}
/>
@@ -88,21 +123,27 @@ const Contact = () => {
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full px-4 py-2 border border-accent-lake rounded-lg focus:outline-none focus:border-primary"
required
+ pattern="[^@\s]+@[^@\s]+\.[^@\s]+"
/>
-
@@ -115,6 +156,8 @@ const Contact = () => {
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
className="w-full px-4 py-2 border border-accent-lake rounded-lg focus:outline-none focus:border-primary h-32"
required
+ minLength={10}
+ maxLength={1000}
/>
@@ -122,9 +165,14 @@ const Contact = () => {
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
- className="w-full bg-primary text-accent-snow py-3 rounded-lg font-semibold hover:bg-secondary transition-colors"
+ disabled={isSubmitting}
+ className={`w-full py-3 rounded-lg font-semibold transition-colors ${
+ isSubmitting
+ ? 'bg-accent-mountain text-accent-snow cursor-not-allowed'
+ : 'bg-primary text-accent-snow hover:bg-secondary'
+ }`}
>
- Send Message
+ {isSubmitting ? 'Sending...' : 'Send Message'}
diff --git a/tailwind.config.js b/tailwind.config.js
index fecdac3..9924918 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -18,4 +18,7 @@ export default {
},
},
plugins: [],
+ corePlugins: {
+ columns: false // This will disable the column utilities
+ }
}
diff --git a/tsconfig.server.json b/tsconfig.server.json
new file mode 100644
index 0000000..7ee1c66
--- /dev/null
+++ b/tsconfig.server.json
@@ -0,0 +1,13 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "allowJs": true,
+ "outDir": "dist"
+ },
+ "include": ["server.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
index fc7d0dc..8cc3b53 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -12,5 +12,26 @@ export default defineConfig({
port: 804,
host: true,
strictPort: true
+ },
+ server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:804',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, '')
+ }
+ }
+ },
+ define: {
+ 'process.env': {
+ SMTP_HOST: process.env.SMTP_HOST,
+ SMTP_PORT: process.env.SMTP_PORT,
+ SMTP_SECURE: process.env.SMTP_SECURE,
+ RECIPIENT_EMAIL: process.env.RECIPIENT_EMAIL,
+ GOOGLE_EMAIL: process.env.GOOGLE_EMAIL,
+ GOOGLE_APP_PASSWORD: process.env.GOOGLE_APP_PASSWORD,
+ UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
+ UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN
+ }
}
-})
\ No newline at end of file
+})